DEV Community

Cover image for How they made controls at Github? Dropdown
Kiran Mantha
Kiran Mantha

Posted on • Edited on

How they made controls at Github? Dropdown

Did you observe the cover image of this article?? if yes, then that's the answer to this article's title.

Yes github makes use of details and summary html5 tags to create controls like:

You can treat this as television series but don't afraid this won't go forever like ever 😌. we got very few episodes that u can count using single hand 👏 😆

Now in Episode1 let's see how they created a dropdown using details and summary tags.

let's create a basic details and summary structure:

<details role='dropdown'>
    <summary>I'm a dropdown</summary>
</details>
Enter fullscreen mode Exit fullscreen mode

if observed above snippet we're using role attribute to define the usecase.

When the above snippet got rendered we just see an accordion with empty content. But a dropdown contains a list of options right let's add them

<details role='dropdown'>
    <summary>I'm a dropdown</summary>
    <ul>
       <li>
          <input id='1' type='radio' name='dropdown'/>
          <label for='1'>Option 1</label>
       </li>
       <li>
          <input id='2' type='radio' name='dropdown'/>
          <label for='2'>Option 2</label>
       </li>
       <li>
          <input id='3' type='radio' name='dropdown'/>
          <label for='3'>Option 3</label>
       </li>
       <li>
          <input id='4' type='radio' name='dropdown'/>
          <label for='4'>Option 4</label>
       </li>
    </ul>
</details>
Enter fullscreen mode Exit fullscreen mode

great this will render an accordion with a list of 4 options with radio buttons. whenever you select an option, the previous selection is cleared because we make use of name attibute on radio button list. now let's style this as a dropdown.

$padder: 0.5rem 1rem;
$trigger-border-color: #ccc;
$option-hover-color: #eee;
$chevron-color: #3273dc;
$checkmark-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
$chevron-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgba(65, 84, 98, 0.999)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");

details[role='dropdown'] {
  position: relative;

  &.disabled {
    cursor: not-allowed;
    & > summary {
      pointer-events: none;
      color: rgba(16, 16, 16, 0.3);
    }
  }

  & > summary {
    list-style: none;
    position: relative;
    cursor: pointer;
    padding: $padder;
    line-height: 1;
    color: inherit;
    border: 1px solid $trigger-border-color;
    border-radius: 2px;

    &::-webkit-details-marker {
      display: none;
    }

    &::after {
      content: '';
      height: 20px;
      width: 20px;
      pointer-events: none;
      background-image: $chevron-url;
      background-repeat: no-repeat;
      background-position: center;
      float: right;
      margin-top: -2px;
      margin-left: 10px;
    }
  }

  &[open] > summary {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;

    &::after {
      transform: rotate(180deg);
    }
  }

  summary + ul {
    position: absolute;
    top: auto;
    left: 0px;
    right: 0px;
    border: 1px solid $trigger-border-color;
    border-top: none;
    border-bottom-left-radius: 2px;
    border-bottom-right-radius: 2px;
    background-color: #fff;
    z-index: 100;
    list-style: none;
    padding: 0;
    margin: 0;

    li {
      box-sizing: border-box;
      padding: 0;
      color: inherit;

      input[type='radio'] {
        position: absolute;
        opacity: 0;

        &:checked + label {
          border-left-color: $chevron-color;
        }
      }

      label {
        cursor: pointer;
        color: inherit;
        display: block;
        position: relative;
        padding: $padder;
        border-left: 3px solid transparent;
        white-space: nowrap;

        &:hover {
          background-color: $option-hover-color;
        }
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

excellent. we made an awesome dropdown. But there's an issue here. If you open the dropdown, whenever you click outside the dropdown should close which is not happening right now.

The immediate solution which come across your mind is adding an event listener to window to close drodown. right?? But what if i say we don't have to use javascript to close the dropdown. Then what's the solution?? css. Yes we can leverage css to close dropdown when we click outside. lets's see how.

To the above scss snippet, add before pseudo selector to &[open] > summary {...}

&[open] > summary {
   &::before {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      z-index: 80;
      display: block;
      cursor: default;
      content: ' ';
      background: transparent;
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it. we didn't use a single line of javascript code to create this dropdown.

We can add checkboxes to convert this to multi-select dropdown. in html snippet, convert radio buttons to checkboxes. but we need to add extra css to style the new layout. this is complete css for our dropdown:

$padder: 0.5rem 1rem;
$trigger-border-color: #ccc;
$option-hover-color: #eee;
$chevron-color: #3273dc;
$checkmark-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
$chevron-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgba(65, 84, 98, 0.999)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");

details[role='dropdown'] {
  position: relative;

  &.disabled {
    cursor: not-allowed;
    & > summary {
      pointer-events: none;
      color: rgba(16, 16, 16, 0.3);
    }
  }

  & > summary {
    list-style: none;
    position: relative;
    cursor: pointer;
    padding: $padder;
    line-height: 1;
    color: inherit;
    border: 1px solid $trigger-border-color;
    border-radius: 2px;

    &::-webkit-details-marker {
      display: none;
    }

    &::after {
      content: '';
      height: 20px;
      width: 20px;
      pointer-events: none;
      background-image: $chevron-url;
      background-repeat: no-repeat;
      background-position: center;
      float: right;
      margin-top: -2px;
      margin-left: 10px;
    }
  }

  &[open] > summary {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;

    &::before {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      z-index: 80;
      display: block;
      cursor: default;
      content: ' ';
      background: transparent;
    }
    &::after {
      transform: rotate(180deg);
    }
  }

  summary + ul {
    position: absolute;
    top: auto;
    left: 0px;
    right: 0px;
    border: 1px solid $trigger-border-color;
    border-top: none;
    border-bottom-left-radius: 2px;
    border-bottom-right-radius: 2px;
    background-color: #fff;
    z-index: 100;
    list-style: none;
    padding: 0;
    margin: 0;

    li {
      box-sizing: border-box;
      padding: 0;
      color: inherit;

      input[type='radio'],
      input[type='checkbox'] {
        position: absolute;
        opacity: 0;

        &:checked + label {
          border-left-color: $chevron-color;
        }
      }

      input[type='checkbox'] {
        & + label:before {
          content: '';
          margin-right: 4px;
          display: inline-block;
          width: 20px;
          height: 20px;
          background-color: #fff;
          border: 1px solid;
          vertical-align: text-bottom;
        }

        &:checked + label:before {
          background-image: $checkmark-url;
          background-repeat: no-repeat;
          background-position: center;
          background-size: calc(1rem * 0.8) auto;
          background-color: $chevron-color;
          border: 1px solid $chevron-color;
        }
      }

      label {
        cursor: pointer;
        color: inherit;
        display: block;
        position: relative;
        padding: $padder;
        border-left: 3px solid transparent;
        white-space: nowrap;

        &:hover {
          background-color: $option-hover-color;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Awesome 😍 we created a dropdown out of html and css. But..

There are 2 caveats.

  1. We have to maually close the dropdown when we are using this dropdown for single select. Whenever an option is selected, then remove open attribute on details tag using detailsTag.removeAttribute('open').
  2. update the summary tag text when an option is selected or comma seperated options in case of multi-select.

This is how Github make use of details and summary tag for dropdown and that's the end of this episode.

You can find the working example here.

EndTitles

Do you know there is a very small css library(~10kb) which helps us in getting freedom from the clutches of remembering n number of helper classes just to design your layout??

if not then, welcome to PicoCSS. Just write semantic html for your layout and leave the rest to PicoCSS. It comes with built-in responsive layouts and light/dark modes. I raised a PR for above dropdown implementation.

I'm not mentioning this library because i raised a PR but i love how the contributors make leverage of html5 and css to create awesome layouts. Go ahead and check their docs.

Meanwhile bring out your thoughts on how we can change the position of dropdown if it is out of viewport and post your solutions in comments section below.

See you in next episode. Thanks..
Kiran 👋

Top comments (1)

Collapse
 
kiranmantha profile image
Kiran Mantha

Hi All, i updated the stackblitz link for current and future implementations. Please check.