<div class="custom__container">
  <label for="language__combo">语言:</label>
  <div id='custom-select-status' class='sr-only' aria-live="polite"></div>
  <div class="custom__select" id="js__custom__select">
    <input type="text" id="language__combo" role="combobox" aria-autocomplete="list" aria-haspopup="listbox" aria-expanded="true" aria-controls="a11y__language__listbox" aria-activedescendant="selected_option" aria-describedby="custom__select__info" tabindex="0" class="select__input" />
    <span id="custom__select__info" class="sr-only">Arrow down for options or start typing to filter</span>
    <span class="custom__select__icons">
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" focusable="false" aria-hidden="true" id="icon__circle__down" class="icon" role="img" fill="currentColor">
        <path d="M16 8c0-4.418-3.582-8-8-8s-8 3.582-8 8 3.582 8 8 8 8-3.582 8-8zM1.5 8c0-3.59 2.91-6.5 6.5-6.5s6.5 2.91 6.5 6.5-2.91 6.5-6.5 6.5-6.5-2.91-6.5-6.5z"></path>
        <path d="M4.957 5.543l-1.414 1.414 4.457 4.457 4.457-4.457-1.414-1.414-3.043 3.043z"></path>
      </svg>
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" focusable="false" aria-hidden="true" id="icon__circle__up" class="icon" role="img" fill="currentColor">
        <path d="M0 8c0 4.418 3.582 8 8 8s8-3.582 8-8-3.582-8-8-8-8 3.582-8 8zM14.5 8c0 3.59-2.91 6.5-6.5 6.5s-6.5-2.91-6.5-6.5 2.91-6.5 6.5-6.5 6.5 2.91 6.5 6.5z"></path>
        <path d="M11.043 10.457l1.414-1.414-4.457-4.457-4.457 4.457 1.414 1.414 3.043-3.043z"></path>
      </svg>
    </span>
    <ul role="listbox" id="a11y__language__listbox" class="select" aria-expanded="false">
      <li role="option" tabindex="-1" class="option">CSS</li>
      <li role="option" tabindex="-1" class="option" id="selected_option">HTML</li>
      <li role="option" tabindex="-1" class="option">JavaScript</li>
      <li role="option" tabindex="-1" class="option">React</li>
      <li role="option" tabindex="-1" class="option">Vue</li>
    </ul>
  </div>
</div>
@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;
}

.custom__container {
  label {
    display: block;
    margin-bottom: 2vh;
  }
}

.custom__select {
  border: 1px solid currentColor;
  border-radius: 3px;
  position: relative;
  color: #fff;

  &[aria-expanded="false"] #icon__circle__up {
    display: none;
  }

  &[aria-expanded="true"] #icon__circle__down {
    display: none;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  -webkit-clip-path: inset(50%);
  clip-path: inset(50%);
  border: 0;
}

.select__input {
  display: block;
  font-size: 1em;
  font-weight: 700;
  padding: 0.6em 46px 0.5em 0.8em;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  margin: 0;
  border: none 0;
  box-shadow: 0 1px 0 1px rgba(0, 0, 0, 0.04);
  appearance: none;
  background-color: #fff;
  z-index: 10;
  color: #333;
}

.custom__select__icons {
  position: absolute;
  top: 0;
  right: 0;
  width: 46px;
  color: #333;
  height: 46px;

  svg {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}

.select {
  display: flex;
  position: absolute;
  flex-direction: column;
  border: 1px solid #fff;
  left: -1px;
  right: -1px;
  border-radius: 0 0 3px 3px;
  background: #fff;
  margin: 0;
  padding: 0;
  list-style: none outside none;
  color: #333;

  &.hidden-all {
    display: none;
  }

  li {
    cursor: pointer;
    padding: 6px 12px;
    display: flex;
    align-items: center;

    &.active,
    &:hover {
      background: #007fff;
      color: #fff;
    }
  }
}
View Compiled
// From: https://24ways.org/2019/making-a-better-custom-select-element/
// Author: @Julie Grundy
// Github: https://github.com/stringyland/custom-select

const customSelect = function(element, overrides) {
  const defaults = {
    inputSelector: "input",
    listSelector: "ul",
    optionSelector: "li",
    statusSelector: '[aria-live="polite"]'
  };

  const options = Object.assign({}, defaults, overrides);

  const csSelector = document.querySelector(element); // the input, svg and ul as a group
  const csInput = csSelector.querySelector(options.inputSelector);
  const csList = csSelector.querySelector(options.listSelector);
  const csOptions = csList.querySelectorAll(options.optionSelector);
  const csStatus = document.querySelector(options.statusSelector);
  const aOptions = Array.from(csOptions);

  let csState = "initial";

  csSelector.addEventListener("click", function(e) {
    const currentFocus = findFocus();
    switch (csState) {
      case "initial":
        toggleList("Open");
        setState("opened");
        break;
      case "opened":
        if (currentFocus === csInput) {
          toggleList("Shut");
          setState("initial");
        } else if (currentFocus.tagName === "LI") {
          makeChoice(currentFocus);
          toggleList("Shut");
          setState("closed");
        }
        break;
      case "filtered":
        if (currentFocus.tagName === "LI") {
          makeChoice(currentFocus);
          toggleList("Shut");
          setState("closed");
        }

        break;
      case "closed":
        toggleList("Open");
        setState("filtered");
        break;
    }
  });

  csSelector.addEventListener("keyup", function(e) {
    doKeyAction(e.key);
  });

  document.addEventListener("click", function(e) {
    if (!e.target.closest(element)) {
      toggleList("Shut");
      setState("initial");
    }
  });

  function toggleList(whichWay) {
    if (whichWay === "Open") {
      csList.classList.remove("hidden-all");
      csSelector.setAttribute("aria-expanded", "true");
    } else {
      csList.classList.add("hidden-all");
      csSelector.setAttribute("aria-expanded", "false");
    }
  }

  function findFocus() {
    const focusPoint = document.activeElement;
    return focusPoint;
  }

  function moveFocus(fromHere, toThere) {
    const aCurrentOptions = aOptions.filter(function(option) {
      if (option.style.display === "") {
        return true;
      }
    });
    if (aCurrentOptions.length === 0) {
      return;
    }
    if (toThere === "input") {
      csInput.focus();
    }
    switch (fromHere) {
      case csInput:
        if (toThere === "forward") {
          aCurrentOptions[0].focus();
        } else if (toThere === "back") {
          aCurrentOptions[aCurrentOptions.length - 1].focus();
        }
        break;
      case csOptions[0]:
        if (toThere === "forward") {
          aCurrentOptions[1].focus();
        } else if (toThere === "back") {
          csInput.focus();
        }
        break;
      case csOptions[csOptions.length - 1]:
        if (toThere === "forward") {
          aCurrentOptions[0].focus();
        } else if (toThere === "back") {
          aCurrentOptions[aCurrentOptions.length - 2].focus();
        }
        break;
      default:
        const currentItem = findFocus();
        const whichOne = aCurrentOptions.indexOf(currentItem);
        if (toThere === "forward") {
          const nextOne = aCurrentOptions[whichOne + 1];
          nextOne.focus();
        } else if (toThere === "back" && whichOne > 0) {
          const previousOne = aCurrentOptions[whichOne - 1];
          previousOne.focus();
        } else {
          csInput.focus();
        }
        break;
    }
  }

  function doFilter() {
    const terms = csInput.value;
    const aFilteredOptions = aOptions.filter(function(option) {
      if (option.innerText.toUpperCase().startsWith(terms.toUpperCase())) {
        return true;
      }
    });
    csOptions.forEach(option => (option.style.display = "none"));
    aFilteredOptions.forEach(function(option) {
      option.style.display = "";
    });
    setState("filtered");
    updateStatus(aFilteredOptions.length);
  }

  function updateStatus(howMany) {
    csStatus.textContent = howMany + " options available.";
  }

  function makeChoice(whichOption) {
    csInput.value = whichOption.textContent;
    moveFocus(document.activeElement, "input");
  }

  function setState(newState) {
    switch (newState) {
      case "initial":
        csState = "initial";
        break;
      case "opened":
        csState = "opened";
        break;
      case "filtered":
        csState = "filtered";
        break;
      case "closed":
        csState = "closed";
    }
  }

  function doKeyAction(whichKey) {
    const currentFocus = findFocus();
    switch (whichKey) {
      case "Enter":
        if (csState === "initial") {
          toggleList("Open");
          setState("opened");
        } else if (csState === "opened" && currentFocus.tagName === "LI") {
          makeChoice(currentFocus);
          toggleList("Shut");
          setState("closed");
        } else if (csState === "opened" && currentFocus === csInput) {
          toggleList("Shut");
          setState("closed");
        } else if (csState === "filtered" && currentFocus.tagName === "LI") {
          makeChoice(currentFocus);
          toggleList("Shut");
          setState("closed");
        } else if (csState === "filtered" && currentFocus === csInput) {
          toggleList("Open");
          setState("opened");
        } else {
          toggleList("Open");
          setState("filtered");
        }
        break;

      case "Escape":
        if (csState === "opened" || csState === "filtered") {
          toggleList("Shut");
          setState("initial");
        }
        break;

      case "ArrowDown":
        if (csState === "initial" || csState === "closed") {
          toggleList("Open");
          moveFocus(csInput, "forward");
          setState("opened");
        } else {
          toggleList("Open");
          moveFocus(currentFocus, "forward");
        }
        break;
      case "ArrowUp":
        if (csState === "initial" || csState === "closed") {
          toggleList("Open");
          moveFocus(csInput, "back");
          setState("opened");
        } else {
          moveFocus(currentFocus, "back");
        }
        break;
      default:
        if (csState === "initial") {
          toggleList("Open");
          doFilter();
          setState("filtered");
        } else if (csState === "opened") {
          doFilter();
          setState("filtered");
        } else if (csState === "closed") {
          doFilter();
          setState("filtered");
        } else {
          doFilter();
        }
        break;
    }
  }
};

customSelect("#js__custom__select", {
  statusSelector: "#custom-select-status"
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.