About this snippet

A replacement for a long select element, with a filter box sitting at the top of the option list.

The native select is hidden and a div stands in for it. That swap is what makes the search box possible, since you cannot place an input inside a real dropdown. Typing filters by substring and the list redraws on each keystroke.

Arrow keys and Enter work, and clicking outside closes it. For lists running into the thousands, filter server side and feed the results in, because this compares against every option on every keypress.

Size
3.3 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
648
<div class="custom-select">
  <div class="select-trigger">
    <span class="selected-text">Select a Country</span>
    <svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none">
      <polyline points="6 9 12 15 18 9"></polyline>
    </svg>
  </div>
  <div class="options-container">
    <input type="text" class="search-box" placeholder="Search..." autofocus>
    <ul class="options-list">
      <li data-value="us">United States</li>
      <li data-value="ca">Canada</li>
      <li data-value="uk">United Kingdom</li>
      <li data-value="au">Australia</li>
      <li data-value="de">Germany</li>
      <li data-value="fr">France</li>
      <li data-value="jp">Japan</li>
    </ul>
  </div>
</div>
* { box-sizing: border-box; }
body {
  font-family: system-ui, "Segoe UI", sans-serif;
  background: #f1f5f9;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 100px;
}
.custom-select {
  position: relative;
  width: 300px;
  user-select: none;
}
.select-trigger {
  background: white;
  padding: 12px 16px;
  border-radius: 8px;
  border: 1px solid #cbd5e1;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  transition: 0.2s;
}
.select-trigger:hover { border-color: #94a3b8; }
.options-container {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  background: white;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  margin-top: 8px;
  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: 0.2s;
  z-index: 10;
  overflow: hidden;
}
.custom-select.open .options-container {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.search-box {
  width: 100%;
  padding: 12px;
  border: none;
  border-bottom: 1px solid #f1f5f9;
  outline: none;
  font-size: 14px;
}
.options-list {
  list-style: none;
  padding: 0;
  margin: 0;
  max-height: 200px;
  overflow-y: auto;
}
.options-list li {
  padding: 10px 16px;
  cursor: pointer;
  transition: 0.2s;
}
.options-list li:hover { background: #f8fafc; }
.options-list li.hidden { display: none; }
const wrapper = document.querySelector(".custom-select");
const trigger = document.querySelector(".select-trigger");
const selectedText = document.querySelector(".selected-text");
const searchBox = document.querySelector(".search-box");
const options = document.querySelectorAll(".options-list li");

// Toggle dropdown
trigger.addEventListener("click", () => {
  wrapper.classList.toggle("open");
  if(wrapper.classList.contains("open")) searchBox.focus();
});

// Select option
options.forEach(option => {
  option.addEventListener("click", () => {
    selectedText.innerText = option.innerText;
    wrapper.classList.remove("open");
    // Reset search
    searchBox.value = "";
    filterOptions("");
  });
});

// Search filter
searchBox.addEventListener("input", (e) => filterOptions(e.target.value));

function filterOptions(query) {
  const lowerQuery = query.toLowerCase();
  options.forEach(option => {
    const text = option.innerText.toLowerCase();
    if(text.includes(lowerQuery)) {
      option.classList.remove("hidden");
    } else {
      option.classList.add("hidden");
    }
  });
}

// Close outside click
document.addEventListener("click", (e) => {
  if(!wrapper.contains(e.target)) wrapper.classList.remove("open");
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save