How they made controls at Github? Dropdown
Tags: javascript, css, html, github
Published: 2021-12-31 06:46:33

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:

- Episode1: **Dropdown**
- Episode2: [Modal dialog](https://dev.to/kiranmantha/how-they-made-controls-at-github-modal-dialog-2pgi)
- Episode3: [Hamburger menu](https://dev.to/kiranmantha/how-they-made-controls-at-github-hamburger-menu-20k)
- Episode4: [Popup](https://dev.to/kiranmantha/how-they-made-controls-at-github-popup-2omi)

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

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:

```html
<details role='dropdown'>
    <summary>I'm a dropdown</summary>
</details>
```
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

```html
<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>
``` 
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. 

```scss
$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;
        }
      }
    }
  }
}

```
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 {...}`

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

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:

```scss
$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;
        }
      }
    }
  }
}
```
Awesome :heart_eyes: 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](https://stackblitz.com/edit/details-summary-tag-based-controls?file=index.html).

### 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](https://picocss.com/). 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](https://github.com/picocss/pico/pull/86) 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 :wave: