<!--  
Author: @Andrew Bone 
Tutorial: https://dev.to/link2twenty/accessibility-first-dropdown-select-3ji5
-->
<details id="example_select" class="select_container">
  <summary>--</summary>
  <div class="select">
    <label class="select__option">
      <input type="radio" name="example" value="slower">Slower
    </label>
    <label class="select__option">
      <input type="radio" name="example" value="slow">Slow
    </label>
    <label class="select__option">
      <input checked type="radio" name="example" value="medium">Medium
    </label>
    <label class="select__option">
      <input type="radio" name="example" value="fast">Fast
    </label>
    <label class="select__option">
      <input type="radio" name="example" value="faster">Faster
    </label>
  </div>
</details>
@import url("https://fonts.googleapis.com/css?family=Gochi+Hand");

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  margin: 0;
  padding: 0;
  background-color: #291642;
  font-family: "Gochi Hand", sans-serif;
  color: #fff;
  font-size: 130%;
  letter-spacing: 0.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  min-height: 100vh;
}

details.select_container {
  display: inline-block;
  width: 200px;
  border: 1px solid #c5c5c5;
  border-radius: 3px;
  position: relative;
  color: #454545;
}

details.select_container[open] {
  border-radius: 3px 3px 0 0;
}

details.select_container summary::after {
  content: "\00203A";
  position: absolute;
  right: 12px;
  top: calc(50%);
  transform: translateY(-50%) rotate(90deg);
  pointer-events: none;
}

details.select_container[open] summary::after {
  content: "\002039";
}

details.select_container summary {
  cursor: pointer;
  padding: 6px 12px;
  background: #f6f6f6;
  list-style: none;
}

details.select_container summary::-webkit-details-marker {
  display: none;
}

details.select_container summary:hover {
  background: #ededed;
}

details.select_container .select {
  position: absolute;
  display: flex;
  flex-direction: column;
  border: 1px solid #c5c5c5;
  width: 100%;
  left: -1px;
  border-radius: 0 0 3px 3px;
  background: #fff;
}

details.select_container .select__option {
  cursor: pointer;
  padding: 6px 12px;
}

details.select_container .select:hover .select__option.active {
  background: #fff;
  color: #454545;
}

details.select_container .select__option.active,
details.select_container .select:hover .select__option.active:hover,
details.select_container .select__option:hover {
  background: #007fff;
  color: #fff;
}

details.select_container .select__option input {
  display: none;
}
View Compiled
class detailSelect {
  constructor(container) {
    this.container = document.querySelector(container);
    this.options = document.querySelectorAll(
      `${container} > .select > .select__option`
    );
    this.value = this.container.querySelector("summary").textContent;
    this.mouseDown = false;
    this._addEventListeners();
    this._setAria();
    this.updateValue();
  }

  // Private function to set event listeners
  _addEventListeners() {
    this.container.addEventListener("toggle", () => {
      if (this.container.open) return;
      this.updateValue();
    });

    this.container.addEventListener("focusout", e => {
      if (this.mouseDown) return;
      this.container.removeAttribute("open");
    });

    this.options.forEach(opt => {
      opt.addEventListener("mousedown", () => {
        this.mouseDown = true;
      });
      opt.addEventListener("mouseup", () => {
        this.mouseDown = false;
        this.container.removeAttribute("open");
      });
    });

    this.container.addEventListener("keyup", e => {
      const keycode = e.which;
      const current = [...this.options].indexOf(
        this.container.querySelector(".active")
      );
      switch (keycode) {
        case 27: // ESC
          this.container.removeAttribute("open");
          break;
        case 35: // END
          e.preventDefault();
          if (!this.container.open) this.container.setAttribute("open", "");
          this.setChecked(
            this.options[this.options.length - 1].querySelector("input")
          );
          break;
        case 36: // HOME
          e.preventDefault();
          if (!this.container.open) this.container.setAttribute("open", "");
          this.setChecked(this.options[0].querySelector("input"));
          break;
        case 38: // UP
          e.preventDefault();
          if (!this.container.open) this.container.setAttribute("open", "");
          this.setChecked(
            this.options[current > 0 ? current - 1 : 0].querySelector("input")
          );
          break;
        case 40: // DOWN
          e.preventDefault();
          if (!this.container.open) this.container.setAttribute("open", "");
          this.setChecked(
            this.options[
              current < this.options.length - 1
                ? current + 1
                : this.options.length - 1
            ].querySelector("input")
          );
          break;
      }
    });
  }

  _setAria() {
    this.container.setAttribute("aria-haspopup", "listbox");
    const selectBox = this.container.querySelector(".select");
    selectBox.setAttribute("role", "listbox");
    selectBox.querySelector("[type=radio]").setAttribute("role", "option");
  }

  updateValue(e) {
    const that = this.container.querySelector("input:checked");
    if (!that) return;
    this.setValue(that);
  }

  setChecked(that) {
    that.checked = true;
    this.setValue(that);
  }

  setValue(that) {
    if (this.value == that.value) return;

    this.container.querySelector("summary").textContent =
      that.parentNode.textContent;
    this.value = that.value;

    this.options.forEach(opt => {
      opt.classList.remove("active");
    });
    that.parentNode.classList.add("active");

    this.container.dispatchEvent(new Event("change"));
  }
}

const details = new detailSelect("#example_select");

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.