Open demo

CSS code

HTML
<div><span class="lbl">Native selects on a dark surface</span><div class="card">
<div class="dk-row">
  <div class="dk"><label class="dk-label" for="dk-level">Log level</label>
    <select class="dk-sel" id="dk-level" name="level"><option>Debug</option><option selected>Info</option><option>Warning</option><option>Error</option></select></div>
  <div class="dk"><label class="dk-label" for="dk-env">Environment</label>
    <select class="dk-sel" id="dk-env" name="environment"><option selected>Production</option><option>Staging</option><option>Preview</option></select></div>
</div>
</div></div>

<div><span class="lbl">Multiselect with select all and a count</span><div class="card">
<div class="dk" data-select data-names="3">
  <label class="dk-label" id="dk-rg-label" for="dk-rg-native">Deploy to regions</label>
  <select class="dk-sel dk-native" id="dk-rg-native" name="regions" multiple size="7"><option value="fra-1" selected>Frankfurt</option><option value="sto-1" selected>Stockholm</option><option value="lon-2" selected>London</option><option value="iad-1">Virginia</option><option value="pdx-1">Oregon</option><option value="sin-1">Singapore</option><option value="tyo-1">Tokyo</option></select>
  <div class="dk-ui" data-ui hidden>
    <div class="dk-combo" id="dk-rg-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-rg-list" aria-labelledby="dk-rg-label">
      <svg class="dk-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 3.7 5.7 3.7 9s-1.2 6.3-3.7 9c-2.5-2.7-3.7-5.7-3.7-9S9.5 5.7 12 3Z"/></svg>
      <span class="dk-shown" data-shown data-placeholder="No regions" data-all-text="All regions">No regions</span>
      <span class="dk-count" data-count aria-hidden="true" hidden>0</span>
      <svg class="dk-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="dk-pop" data-popup hidden>
      <div class="dk-list" role="listbox" id="dk-rg-list" aria-labelledby="dk-rg-label" aria-multiselectable="true" tabindex="-1">
        <div class="dk-opt dk-all" role="option" id="dk-rg-all" data-all aria-selected="false"><span class="dk-box" aria-hidden="true"></span>Select all</div>
        <div class="dk-opt" role="option" id="dk-rg-1" data-value="fra-1" data-label="Frankfurt" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Frankfurt <span class="dk-sub">fra-1</span></span><span class="dk-ms">18 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-2" data-value="sto-1" data-label="Stockholm" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Stockholm <span class="dk-sub">sto-1</span></span><span class="dk-ms">31 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-3" data-value="lon-2" data-label="London" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">London <span class="dk-sub">lon-2</span></span><span class="dk-ms">24 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-4" data-value="iad-1" data-label="Virginia" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Virginia <span class="dk-sub">iad-1</span></span><span class="dk-ms">92 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-5" data-value="pdx-1" data-label="Oregon" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Oregon <span class="dk-sub">pdx-1</span></span><span class="dk-ms">151 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-6" data-value="sin-1" data-label="Singapore" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Singapore <span class="dk-sub">sin-1</span></span><span class="dk-ms">164 ms</span></div>
        <div class="dk-opt" role="option" id="dk-rg-7" data-value="tyo-1" data-label="Tokyo" aria-selected="false"><span class="dk-box" aria-hidden="true"></span><span class="dk-main">Tokyo <span class="dk-sub">tyo-1</span></span><span class="dk-ms">218 ms</span></div>
      </div>
    </div>
  </div>
</div>
</div></div>

<div><span class="lbl">Grouped single select with specs and prices</span><div class="card">
<div class="dk" data-select>
  <label class="dk-label" id="dk-sz-label" for="dk-sz-native">Instance size</label>
  <select class="dk-sel dk-native" id="dk-sz-native" name="size">
    <optgroup label="General purpose"><option value="s1">Starter S1</option><option value="s2" selected>Starter S2</option><option value="b2">Balanced B2</option></optgroup>
    <optgroup label="Compute optimised"><option value="c4">Compute C4</option><option value="c8">Compute C8</option></optgroup>
    <optgroup label="Memory optimised"><option value="m16">Memory M16</option></optgroup>
  </select>
  <div class="dk-ui" data-ui hidden>
    <div class="dk-combo has-pic" id="dk-sz-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-sz-list" aria-labelledby="dk-sz-label">
      <span class="dk-shown has-pic" data-shown data-placeholder="Choose a size">Choose a size</span>
      <svg class="dk-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="dk-pop" data-popup hidden>
      <div class="dk-list" role="listbox" id="dk-sz-list" aria-labelledby="dk-sz-label" tabindex="-1">
        <div role="group" aria-labelledby="dk-sz-g1">
          <div class="dk-group" role="presentation" id="dk-sz-g1">General purpose</div>
          <div class="dk-opt" role="option" id="dk-sz-1" data-value="s1" data-label="Starter S1" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Starter S1 <span class="dk-sub">1 vCPU · 2 GB</span></span><span class="dk-ms">$0.012/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dk-opt" role="option" id="dk-sz-2" data-value="s2" data-label="Starter S2" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Starter S2 <span class="dk-sub">2 vCPU · 4 GB</span></span><span class="dk-ms">$0.024/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dk-opt" role="option" id="dk-sz-3" data-value="b2" data-label="Balanced B2" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Balanced B2 <span class="dk-sub">2 vCPU · 8 GB</span></span><span class="dk-ms">$0.058/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g2">
          <div class="dk-group" role="presentation" id="dk-sz-g2">Compute optimised</div>
          <div class="dk-opt" role="option" id="dk-sz-4" data-value="c4" data-label="Compute C4" aria-selected="false"><span class="dk-pic" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="dk-main">Compute C4 <span class="dk-sub">4 vCPU · 8 GB</span></span><span class="dk-ms">$0.102/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dk-opt" role="option" id="dk-sz-5" data-value="c8" data-label="Compute C8" aria-selected="false"><span class="dk-pic" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="dk-main">Compute C8 <span class="dk-sub">8 vCPU · 16 GB</span></span><span class="dk-ms">$0.204/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g3">
          <div class="dk-group" role="presentation" id="dk-sz-g3">Memory optimised</div>
          <div class="dk-opt" role="option" id="dk-sz-6" data-value="m16" data-label="Memory M16" aria-selected="false"><span class="dk-pic" style="--tone:#6ee7b7" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 11v2M11 11v2M15 11v2M7 17v3M17 17v3"/></svg></span><span class="dk-main">Memory M16 <span class="dk-sub">2 vCPU · 16 GB</span></span><span class="dk-ms">$0.091/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
      </div>
    </div>
  </div>
</div>
</div></div>
CSS
/* Dark multiselects: styled native selects with color-scheme: dark, and select-only comboboxes (WAI-ARIA APG) over
   multi- and single-select listboxes. Lines stay at 3:1 on the dark card, text at 4.5:1, and the focus ring is a
   light indigo that shows on the dark field. The script sets aria-expanded, data-active and data-chosen. */
.dk{--dk-accent:#818cf8;--dk-accent-ink:#0b0f17;--dk-ring:rgba(129,140,248,.4);--dk-line:#6b7794;--dk-field:#0f1522;--dk-pop:#161d2c;
  --dk-active:#232c44;--dk-ink:#e6ebf5;--dk-muted:#9aa6bd;
  --dk-chev:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239aa6bd' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  --dk-tick:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 8.5 3 3 6-7'/%3E%3C/svg%3E");
  --dk-dash:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E");
  position:relative;display:grid;grid-template-columns:minmax(0,1fr);gap:7px;min-width:0;color:var(--dk-ink);color-scheme:dark}
.dk [hidden]{display:none!important}
.dk-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px}
.dk-label{font-size:.88rem;font-weight:600}
.dk-sel,.dk-native{-webkit-appearance:none;appearance:none;display:block;width:100%;height:44px;margin:0;padding:0 40px 0 13px;font:inherit;font-size:.94rem;
  color:var(--dk-ink);background:var(--dk-field) var(--dk-chev) no-repeat right 13px center/16px;border:1px solid var(--dk-line);border-radius:10px;cursor:pointer;
  transition:border-color .15s,box-shadow .15s}
.dk-native[multiple]{height:auto;padding:6px;background-image:none}
.dk-sel:hover{border-color:#8a95b0}
.dk-sel:focus-visible{outline:none;border-color:var(--dk-accent);box-shadow:0 0 0 4px var(--dk-ring)}
.dk-ui{position:relative}
.dk-combo{display:flex;align-items:center;gap:10px;height:46px;padding:0 13px;font-size:.94rem;background:var(--dk-field);border:1px solid var(--dk-line);
  border-radius:10px;cursor:pointer;user-select:none;transition:border-color .15s,box-shadow .15s}
.dk-combo.has-pic{padding-left:8px}
.dk-combo:hover{border-color:#8a95b0}
.dk-combo:focus-visible,.dk-combo[aria-expanded="true"]{outline:none;border-color:var(--dk-accent);box-shadow:0 0 0 4px var(--dk-ring)}
.dk-ico{flex:none;width:18px;height:18px;color:var(--dk-accent)}
.dk-shown{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dk-shown.has-pic{display:flex;align-items:center;gap:10px;font-weight:600}
.dk-shown .dk-pic{width:30px;height:30px}
.dk-combo[data-empty] .dk-shown{color:var(--dk-muted)}
.dk-count{flex:none;display:grid;place-items:center;min-width:22px;height:22px;padding:0 7px;border-radius:999px;background:var(--dk-accent);color:var(--dk-accent-ink);font-size:.75rem;font-weight:800}
.dk-chev{flex:none;width:16px;height:16px;color:var(--dk-muted);transition:transform .2s}
.dk-combo[aria-expanded="true"] .dk-chev{transform:rotate(180deg)}
.dk-pop{position:absolute;z-index:30;left:0;right:0;top:calc(100% + 6px);padding:6px;background:var(--dk-pop);border:1px solid #2e3850;border-radius:12px;
  box-shadow:0 22px 44px -14px rgba(0,0,0,.7)}
.dk-list{max-height:272px;overflow-y:auto;overscroll-behavior:contain;outline:none;scrollbar-color:#3a4560 transparent}
.dk-group{padding:9px 10px 4px;font-size:.68rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--dk-muted)}
.dk-opt{display:flex;align-items:center;gap:11px;padding:7px 10px;border-radius:8px;font-size:.92rem;cursor:pointer}
.dk-opt[data-active]{background:var(--dk-active)}
.dk-box{flex:none;width:18px;height:18px;border:1.5px solid #7d89a6;border-radius:5px;background:transparent center/13px no-repeat}
.dk-opt[data-chosen] .dk-box{background-color:var(--dk-accent);background-image:var(--dk-tick);border-color:var(--dk-accent)}
.dk-opt[data-mixed] .dk-box{background-color:var(--dk-accent);background-image:var(--dk-dash);border-color:var(--dk-accent)}
.dk-all{position:relative;margin-bottom:7px;font-weight:600}
.dk-all::after{content:"";position:absolute;left:10px;right:10px;bottom:-4px;height:1px;background:#2a3346}
.dk-main{flex:1;min-width:0;font-weight:500}
.dk-sub{margin-left:6px;font-size:.78rem;font-weight:400;color:var(--dk-muted);font-family:ui-monospace,Menlo,Consolas,monospace}
@media (max-width:480px){.dk-sub{display:block;margin-left:0}}
.dk-pic{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:8px;background:#1e2638;color:var(--tone)}
.dk-pic svg{width:17px;height:17px}
.dk-ms{flex:none;font-size:.78rem;color:var(--dk-muted);font-family:ui-monospace,Menlo,Consolas,monospace}
.dk-check{flex:none;width:16px;height:16px;color:var(--dk-accent);visibility:hidden}
.dk-opt[data-chosen] .dk-check{visibility:visible}
@media (prefers-reduced-motion:reduce){.dk-sel,.dk-combo,.dk-chev{transition:none}}
JavaScript
// Select-only combobox with a listbox popup (WAI-ARIA APG), single or multi-select.
// Focus stays on the combobox; aria-activedescendant points at the highlighted option.
// Closed: Down, Up, Enter or Space open it, Home / End open at the first / last option, typing opens and jumps.
// Open: Down / Up / Home / End / Page Up / Page Down move; Enter or Space picks (ticks in a multiselect);
// Ctrl+A ticks all; typing jumps to the next match; Escape closes; Tab closes and moves on.
// The <select> inside each [data-select] holds the real form value and the starting selection. It is hidden
// once this script runs, so without JavaScript the native control is still there and still works.
(() => {
  document.querySelectorAll('[data-select]').forEach(root => {
    const native = root.querySelector('select');
    const label = root.querySelector('label');
    const combo = root.querySelector('[role="combobox"]');
    const popup = root.querySelector('[data-popup]');
    const list = root.querySelector('[role="listbox"]');
    const shown = root.querySelector('[data-shown]');
    const count = root.querySelector('[data-count]');
    const clear = root.querySelector('[data-clear]');
    const multi = native.multiple;
    const opts = [...list.querySelectorAll('[role="option"]')];
    const all = list.querySelector('[data-all]');
    const items = opts.filter(o => o !== all);
    const source = o => [...native.options].find(n => n.value === o.dataset.value);
    const text = o => o.dataset.label || o.textContent.trim();
    const isOpen = () => combo.getAttribute('aria-expanded') === 'true';
    const changed = () => native.dispatchEvent(new Event('change', { bubbles: true }));
    let active = -1, typed = '', typedAt = 0;

    native.hidden = true;
    root.querySelector('[data-ui]').hidden = false;

    function render() {
      const on = items.filter(o => source(o).selected);
      items.forEach(o => {
        o.toggleAttribute('data-chosen', on.includes(o));
        if (multi || !isOpen()) o.setAttribute('aria-selected', on.includes(o));
      });
      if (all) {
        all.setAttribute('aria-selected', on.length === items.length);
        all.toggleAttribute('data-chosen', on.length === items.length);
        all.toggleAttribute('data-mixed', on.length > 0 && on.length < items.length);
      }
      if (!on.length) shown.textContent = shown.dataset.placeholder;
      else if (!multi) {
        const pic = on[0].querySelector('[data-pic]');
        shown.replaceChildren(...(pic ? [pic.cloneNode(true)] : []), text(on[0]));
      } else if (on.length === items.length && shown.dataset.allText) shown.textContent = shown.dataset.allText;
      else shown.textContent = on.length <= (+root.dataset.names || 2) ? on.map(text).join(', ') : on.length + ' selected';
      combo.toggleAttribute('data-empty', !on.length);
      if (count) { count.textContent = on.length; count.hidden = !on.length; }
      if (clear) clear.hidden = !on.length;
    }
    function setActive(i) {
      active = i;
      opts.forEach((o, n) => {
        o.toggleAttribute('data-active', n === i);
        if (!multi && i > -1) o.setAttribute('aria-selected', n === i); // single select: selection follows the highlight
      });
      if (i < 0) return combo.removeAttribute('aria-activedescendant');
      combo.setAttribute('aria-activedescendant', opts[i].id);
      const o = opts[i].getBoundingClientRect(), b = list.getBoundingClientRect(); // keep it in view without scrolling the page
      if (i === 0) list.scrollTop = 0;
      else if (o.top < b.top) list.scrollTop -= b.top - o.top;
      else if (o.bottom > b.bottom) list.scrollTop += o.bottom - b.bottom;
    }
    function open(i) {
      if (!isOpen()) { popup.hidden = false; combo.setAttribute('aria-expanded', 'true'); }
      if (i !== undefined) setActive(Math.max(0, Math.min(opts.length - 1, i)));
    }
    function close() {
      popup.hidden = true;
      combo.setAttribute('aria-expanded', 'false');
      setActive(-1);
      render();
    }
    const current = () => Math.max(0, opts.findIndex(o => o.hasAttribute('data-chosen')));
    function setAll() {
      const every = items.every(o => source(o).selected);
      items.forEach(o => { source(o).selected = !every; });
    }
    function pick(o) {
      if (!multi) native.value = o.dataset.value;
      else if (o === all) setAll();
      else source(o).selected = !source(o).selected;
      render();
      changed();
    }
    function typeahead(ch) {
      const now = Date.now();
      typed = (now - typedAt > 600 ? '' : typed) + ch.toLowerCase();
      typedAt = now;
      const same = [...typed].every(c => c === typed[0]); // pressing one letter again cycles through its matches
      const q = same ? typed[0] : typed;
      const from = active < 0 ? 0 : same ? active + 1 : active;
      for (let k = 0; k < opts.length; k++) {
        const n = (from + k) % opts.length;
        if (text(opts[n]).toLowerCase().startsWith(q)) return open(n);
      }
    }
    const typing = () => typed && Date.now() - typedAt < 600 && opts.some(o => text(o).toLowerCase().startsWith(typed + ' '));

    combo.addEventListener('keydown', e => {
      const k = e.key, last = opts.length - 1;
      const printable = k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;
      if (k.length > 1) typed = ''; // arrows, Home, End… end a type-ahead run
      if (!isOpen()) {
        if (k === 'ArrowDown' || k === 'ArrowUp' || k === 'Enter' || k === ' ') { e.preventDefault(); open(e.altKey ? undefined : current()); }
        else if (k === 'Home') { e.preventDefault(); open(0); }
        else if (k === 'End') { e.preventDefault(); open(last); }
        else if (printable) { e.preventDefault(); open(); typeahead(k); }
        return;
      }
      if (k === ' ' && typing()) { e.preventDefault(); return typeahead(k); } // a space inside a name, as in "In review"
      switch (k) {
        case 'ArrowDown': e.preventDefault(); setActive(Math.min(active + 1, last)); break;
        case 'ArrowUp':
          e.preventDefault();
          if (e.altKey) { if (!multi && active > -1) pick(opts[active]); close(); }
          else setActive(Math.max(active - 1, 0));
          break;
        case 'Home': e.preventDefault(); setActive(0); break;
        case 'End': e.preventDefault(); setActive(last); break;
        case 'PageDown': e.preventDefault(); setActive(Math.min(active + 10, last)); break;
        case 'PageUp': e.preventDefault(); setActive(Math.max(active - 10, 0)); break;
        case 'Enter': case ' ':
          e.preventDefault();
          typed = '';
          if (active > -1) pick(opts[active]);
          if (!multi) close();
          break;
        case 'Escape': e.preventDefault(); close(); break;
        case 'Tab': if (!multi && active > -1) pick(opts[active]); close(); break;
        default:
          if (multi && (e.ctrlKey || e.metaKey) && k.toLowerCase() === 'a') { e.preventDefault(); setAll(); render(); changed(); }
          else if (printable) { e.preventDefault(); typeahead(k); }
      }
    });
    combo.addEventListener('click', () => (isOpen() ? close() : open(current())));
    combo.addEventListener('blur', () => { if (isOpen()) close(); });
    label.addEventListener('click', e => { e.preventDefault(); combo.focus(); });
    popup.addEventListener('mousedown', e => e.preventDefault()); // keep focus on the combobox
    list.addEventListener('click', e => {
      const o = e.target.closest('[role="option"]');
      if (!o) return;
      setActive(opts.indexOf(o));
      pick(o);
      if (!multi) close();
      combo.focus();
    });
    list.addEventListener('pointermove', e => {
      const o = e.target.closest('[role="option"]');
      if (o && !o.hasAttribute('data-active')) setActive(opts.indexOf(o));
    });
    document.addEventListener('pointerdown', e => { if (isOpen() && !root.contains(e.target)) close(); });
    if (clear) clear.addEventListener('click', () => {
      if (multi) [...native.options].forEach(n => { n.selected = false; });
      else native.value = '';
      render();
      changed();
      combo.focus();
    });
    if (native.form) native.form.addEventListener('reset', () => setTimeout(render));
    render();
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Native selects on a dark surface</span><div class="card demo-card"><div class="card-body">
<div class="row g-3">
  <div class="col-sm-6 dk" data-bs-theme="dark"><label class="form-label" for="dk-level">Log level</label>
    <select class="form-select" id="dk-level" name="level"><option>Debug</option><option selected>Info</option><option>Warning</option><option>Error</option></select></div>
  <div class="col-sm-6 dk" data-bs-theme="dark"><label class="form-label" for="dk-env">Environment</label>
    <select class="form-select" id="dk-env" name="environment"><option selected>Production</option><option>Staging</option><option>Preview</option></select></div>
</div>
</div></div></div>

<div><span class="lbl">Multiselect with select all and a count</span><div class="card demo-card"><div class="card-body">
<div class="dk" data-select data-names="3" data-bs-theme="dark">
  <label class="form-label" id="dk-rg-label" for="dk-rg-native">Deploy to regions</label>
  <select class="form-select" id="dk-rg-native" name="regions" multiple size="7"><option value="fra-1" selected>Frankfurt</option><option value="sto-1" selected>Stockholm</option><option value="lon-2" selected>London</option><option value="iad-1">Virginia</option><option value="pdx-1">Oregon</option><option value="sin-1">Singapore</option><option value="tyo-1">Tokyo</option></select>
  <div class="position-relative" data-ui hidden>
    <div class="form-select dk-combo d-flex align-items-center" id="dk-rg-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-rg-list" aria-labelledby="dk-rg-label">
      <svg class="dk-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 3.7 5.7 3.7 9s-1.2 6.3-3.7 9c-2.5-2.7-3.7-5.7-3.7-9S9.5 5.7 12 3Z"/></svg>
      <span class="dk-shown flex-grow-1 text-truncate" data-shown data-placeholder="No regions" data-all-text="All regions">No regions</span>
      <span class="badge rounded-pill dk-count" data-count aria-hidden="true" hidden>0</span>
      <svg class="dk-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="dropdown-menu dk-pop" data-popup hidden>
      <div class="dk-list" role="listbox" id="dk-rg-list" aria-labelledby="dk-rg-label" aria-multiselectable="true" tabindex="-1">
        <div class="dropdown-item dk-opt d-flex align-items-center dk-all" role="option" id="dk-rg-all" data-all aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span>Select all</div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-1" data-value="fra-1" data-label="Frankfurt" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Frankfurt <span class="dk-sub">fra-1</span></span><span class="dk-ms">18 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-2" data-value="sto-1" data-label="Stockholm" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Stockholm <span class="dk-sub">sto-1</span></span><span class="dk-ms">31 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-3" data-value="lon-2" data-label="London" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">London <span class="dk-sub">lon-2</span></span><span class="dk-ms">24 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-4" data-value="iad-1" data-label="Virginia" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Virginia <span class="dk-sub">iad-1</span></span><span class="dk-ms">92 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-5" data-value="pdx-1" data-label="Oregon" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Oregon <span class="dk-sub">pdx-1</span></span><span class="dk-ms">151 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-6" data-value="sin-1" data-label="Singapore" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Singapore <span class="dk-sub">sin-1</span></span><span class="dk-ms">164 ms</span></div>
        <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-rg-7" data-value="tyo-1" data-label="Tokyo" aria-selected="false"><span class="form-check-input dk-box" aria-hidden="true"></span><span class="dk-main">Tokyo <span class="dk-sub">tyo-1</span></span><span class="dk-ms">218 ms</span></div>
      </div>
    </div>
  </div>
</div>
</div></div></div>

<div><span class="lbl">Grouped single select with specs and prices</span><div class="card demo-card"><div class="card-body">
<div class="dk" data-select data-bs-theme="dark">
  <label class="form-label" id="dk-sz-label" for="dk-sz-native">Instance size</label>
  <select class="form-select" id="dk-sz-native" name="size">
    <optgroup label="General purpose"><option value="s1">Starter S1</option><option value="s2" selected>Starter S2</option><option value="b2">Balanced B2</option></optgroup>
    <optgroup label="Compute optimised"><option value="c4">Compute C4</option><option value="c8">Compute C8</option></optgroup>
    <optgroup label="Memory optimised"><option value="m16">Memory M16</option></optgroup>
  </select>
  <div class="position-relative" data-ui hidden>
    <div class="form-select dk-combo d-flex align-items-center has-pic" id="dk-sz-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-sz-list" aria-labelledby="dk-sz-label">
      <span class="dk-shown flex-grow-1 text-truncate has-pic d-flex align-items-center" data-shown data-placeholder="Choose a size">Choose a size</span>
      <svg class="dk-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="dropdown-menu dk-pop" data-popup hidden>
      <div class="dk-list" role="listbox" id="dk-sz-list" aria-labelledby="dk-sz-label" tabindex="-1">
        <div role="group" aria-labelledby="dk-sz-g1">
          <div class="dropdown-header" role="presentation" id="dk-sz-g1">General purpose</div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-1" data-value="s1" data-label="Starter S1" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Starter S1 <span class="dk-sub">1 vCPU · 2 GB</span></span><span class="dk-ms">$0.012/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-2" data-value="s2" data-label="Starter S2" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Starter S2 <span class="dk-sub">2 vCPU · 4 GB</span></span><span class="dk-ms">$0.024/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-3" data-value="b2" data-label="Balanced B2" aria-selected="false"><span class="dk-pic" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="dk-main">Balanced B2 <span class="dk-sub">2 vCPU · 8 GB</span></span><span class="dk-ms">$0.058/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g2">
          <div class="dropdown-header" role="presentation" id="dk-sz-g2">Compute optimised</div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-4" data-value="c4" data-label="Compute C4" aria-selected="false"><span class="dk-pic" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="dk-main">Compute C4 <span class="dk-sub">4 vCPU · 8 GB</span></span><span class="dk-ms">$0.102/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-5" data-value="c8" data-label="Compute C8" aria-selected="false"><span class="dk-pic" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="dk-main">Compute C8 <span class="dk-sub">8 vCPU · 16 GB</span></span><span class="dk-ms">$0.204/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g3">
          <div class="dropdown-header" role="presentation" id="dk-sz-g3">Memory optimised</div>
          <div class="dropdown-item dk-opt d-flex align-items-center" role="option" id="dk-sz-6" data-value="m16" data-label="Memory M16" aria-selected="false"><span class="dk-pic" style="--tone:#6ee7b7" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 11v2M11 11v2M15 11v2M7 17v3M17 17v3"/></svg></span><span class="dk-main">Memory M16 <span class="dk-sub">2 vCPU · 16 GB</span></span><span class="dk-ms">$0.091/h</span><svg class="dk-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
      </div>
    </div>
  </div>
</div>
</div></div></div>
CSS
/* Dark multiselects on Bootstrap 5.3's dark colour mode (data-bs-theme="dark"): .form-select for the native selects,
   a .form-select look for the select-only comboboxes (WAI-ARIA APG), .dropdown-menu, .dropdown-header and .dropdown-item
   for the popups, .form-check-input boxes and a .badge count. The script sets aria-expanded, data-active and data-chosen. */
.dk{--dk-accent:#818cf8;--dk-accent-ink:#0b0f17;--dk-ring:rgba(129,140,248,.4);--dk-line:#6b7794;--dk-field:#0f1522;--dk-pop:#161d2c;
  --dk-active:#232c44;--dk-ink:#e6ebf5;--dk-muted:#9aa6bd;
  --dk-chev:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239aa6bd' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  --dk-tick:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 8.5 3 3 6-7'/%3E%3C/svg%3E");
  --dk-dash:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E");
  position:relative;display:grid;grid-template-columns:minmax(0,1fr);gap:7px;min-width:0;color:var(--dk-ink);color-scheme:dark}
.dk [hidden]{display:none!important} /* beats .d-flex and .badge */
.dk .form-label{margin:0;font-size:.88rem;font-weight:600;color:var(--dk-ink)}
.dk .form-select{--bs-form-select-bg-img:var(--dk-chev);height:44px;padding:0 40px 0 13px;font-size:.94rem;line-height:1.45;color:var(--dk-ink);
  background-color:var(--dk-field);background-position:right 13px center;background-size:16px;border-color:var(--dk-line);border-radius:10px;cursor:pointer}
.dk .form-select[multiple]{height:auto;padding:6px}
.dk .form-select:hover{border-color:#8a95b0}
.dk .form-select:focus{border-color:var(--dk-line);box-shadow:none}
.dk .form-select:focus-visible,.dk .dk-combo[aria-expanded="true"]{border-color:var(--dk-accent);box-shadow:0 0 0 4px var(--dk-ring)}
.dk .dk-combo{--bs-form-select-bg-img:none;gap:10px;height:46px;user-select:none;padding:0 13px}
.dk .dk-combo.has-pic{padding-left:8px}
.dk-ico{flex:none;width:18px;height:18px;color:var(--dk-accent)}
.dk-shown{white-space:nowrap}
.dk-shown.has-pic{gap:10px;font-weight:600}
.dk-shown .dk-pic{width:30px;height:30px}
.dk-combo[data-empty] .dk-shown{color:var(--dk-muted)}
.dk .dk-count{min-width:22px;padding:.3em .6em;background:var(--dk-accent);color:var(--dk-accent-ink);font-size:.75rem;font-weight:800}
.dk-chev{flex:none;width:16px;height:16px;color:var(--dk-muted);transition:transform .2s}
.dk-combo[aria-expanded="true"] .dk-chev{transform:rotate(180deg)}
.dk .dk-pop{--bs-dropdown-bg:var(--dk-pop);--bs-dropdown-padding-x:6px;--bs-dropdown-padding-y:6px;--bs-dropdown-border-color:#2e3850;--bs-dropdown-border-radius:12px;
  --bs-dropdown-link-color:var(--dk-ink);--bs-dropdown-link-hover-color:var(--dk-ink);--bs-dropdown-link-hover-bg:transparent;
  --bs-dropdown-link-active-color:var(--dk-ink);--bs-dropdown-link-active-bg:transparent;--bs-dropdown-header-color:var(--dk-muted);
  --bs-dropdown-header-padding-x:10px;--bs-dropdown-header-padding-y:9px;
  display:block;left:0;right:0;top:calc(100% + 6px);z-index:30;box-shadow:0 22px 44px -14px rgba(0,0,0,.7)}
.dk-list{max-height:272px;overflow-y:auto;overscroll-behavior:contain;outline:none;scrollbar-color:#3a4560 transparent}
.dk .dropdown-header{padding-bottom:4px;font-size:.68rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase}
.dk .dk-opt{gap:11px;padding:7px 10px;border-radius:8px;font-size:.92rem;white-space:normal;cursor:pointer}
.dk .dk-opt[data-active]{background:var(--dk-active)}
.dk .dk-box{flex:none;width:18px;height:18px;margin:0;background-color:transparent;border:1.5px solid #7d89a6;border-radius:5px;background-size:13px}
.dk-opt[data-chosen] .dk-box{--bs-form-check-bg-image:var(--dk-tick);background-color:var(--dk-accent);border-color:var(--dk-accent)}
.dk-opt[data-mixed] .dk-box{--bs-form-check-bg-image:var(--dk-dash);background-color:var(--dk-accent);border-color:var(--dk-accent)}
.dk .dk-all{position:relative;margin-bottom:7px;font-weight:600}
.dk-all::after{content:"";position:absolute;left:10px;right:10px;bottom:-4px;height:1px;background:#2a3346}
.dk-main{flex:1;min-width:0;font-weight:500}
.dk-sub{margin-left:6px;font-size:.78rem;font-weight:400;color:var(--dk-muted);font-family:ui-monospace,Menlo,Consolas,monospace}
@media (max-width:480px){.dk-sub{display:block;margin-left:0}}
.dk-pic{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:8px;background:#1e2638;color:var(--tone)}
.dk-pic svg{width:17px;height:17px}
.dk-ms{flex:none;font-size:.78rem;color:var(--dk-muted);font-family:ui-monospace,Menlo,Consolas,monospace}
.dk-check{flex:none;width:16px;height:16px;color:var(--dk-accent);visibility:hidden}
.dk-opt[data-chosen] .dk-check{visibility:visible}
@media (prefers-reduced-motion:reduce){.dk .form-select,.dk-chev{transition:none}}
JavaScript
// Select-only combobox with a listbox popup (WAI-ARIA APG), single or multi-select.
// Focus stays on the combobox; aria-activedescendant points at the highlighted option.
// Closed: Down, Up, Enter or Space open it, Home / End open at the first / last option, typing opens and jumps.
// Open: Down / Up / Home / End / Page Up / Page Down move; Enter or Space picks (ticks in a multiselect);
// Ctrl+A ticks all; typing jumps to the next match; Escape closes; Tab closes and moves on.
// The <select> inside each [data-select] holds the real form value and the starting selection. It is hidden
// once this script runs, so without JavaScript the native control is still there and still works.
(() => {
  document.querySelectorAll('[data-select]').forEach(root => {
    const native = root.querySelector('select');
    const label = root.querySelector('label');
    const combo = root.querySelector('[role="combobox"]');
    const popup = root.querySelector('[data-popup]');
    const list = root.querySelector('[role="listbox"]');
    const shown = root.querySelector('[data-shown]');
    const count = root.querySelector('[data-count]');
    const clear = root.querySelector('[data-clear]');
    const multi = native.multiple;
    const opts = [...list.querySelectorAll('[role="option"]')];
    const all = list.querySelector('[data-all]');
    const items = opts.filter(o => o !== all);
    const source = o => [...native.options].find(n => n.value === o.dataset.value);
    const text = o => o.dataset.label || o.textContent.trim();
    const isOpen = () => combo.getAttribute('aria-expanded') === 'true';
    const changed = () => native.dispatchEvent(new Event('change', { bubbles: true }));
    let active = -1, typed = '', typedAt = 0;

    native.hidden = true;
    root.querySelector('[data-ui]').hidden = false;

    function render() {
      const on = items.filter(o => source(o).selected);
      items.forEach(o => {
        o.toggleAttribute('data-chosen', on.includes(o));
        if (multi || !isOpen()) o.setAttribute('aria-selected', on.includes(o));
      });
      if (all) {
        all.setAttribute('aria-selected', on.length === items.length);
        all.toggleAttribute('data-chosen', on.length === items.length);
        all.toggleAttribute('data-mixed', on.length > 0 && on.length < items.length);
      }
      if (!on.length) shown.textContent = shown.dataset.placeholder;
      else if (!multi) {
        const pic = on[0].querySelector('[data-pic]');
        shown.replaceChildren(...(pic ? [pic.cloneNode(true)] : []), text(on[0]));
      } else if (on.length === items.length && shown.dataset.allText) shown.textContent = shown.dataset.allText;
      else shown.textContent = on.length <= (+root.dataset.names || 2) ? on.map(text).join(', ') : on.length + ' selected';
      combo.toggleAttribute('data-empty', !on.length);
      if (count) { count.textContent = on.length; count.hidden = !on.length; }
      if (clear) clear.hidden = !on.length;
    }
    function setActive(i) {
      active = i;
      opts.forEach((o, n) => {
        o.toggleAttribute('data-active', n === i);
        if (!multi && i > -1) o.setAttribute('aria-selected', n === i); // single select: selection follows the highlight
      });
      if (i < 0) return combo.removeAttribute('aria-activedescendant');
      combo.setAttribute('aria-activedescendant', opts[i].id);
      const o = opts[i].getBoundingClientRect(), b = list.getBoundingClientRect(); // keep it in view without scrolling the page
      if (i === 0) list.scrollTop = 0;
      else if (o.top < b.top) list.scrollTop -= b.top - o.top;
      else if (o.bottom > b.bottom) list.scrollTop += o.bottom - b.bottom;
    }
    function open(i) {
      if (!isOpen()) { popup.hidden = false; combo.setAttribute('aria-expanded', 'true'); }
      if (i !== undefined) setActive(Math.max(0, Math.min(opts.length - 1, i)));
    }
    function close() {
      popup.hidden = true;
      combo.setAttribute('aria-expanded', 'false');
      setActive(-1);
      render();
    }
    const current = () => Math.max(0, opts.findIndex(o => o.hasAttribute('data-chosen')));
    function setAll() {
      const every = items.every(o => source(o).selected);
      items.forEach(o => { source(o).selected = !every; });
    }
    function pick(o) {
      if (!multi) native.value = o.dataset.value;
      else if (o === all) setAll();
      else source(o).selected = !source(o).selected;
      render();
      changed();
    }
    function typeahead(ch) {
      const now = Date.now();
      typed = (now - typedAt > 600 ? '' : typed) + ch.toLowerCase();
      typedAt = now;
      const same = [...typed].every(c => c === typed[0]); // pressing one letter again cycles through its matches
      const q = same ? typed[0] : typed;
      const from = active < 0 ? 0 : same ? active + 1 : active;
      for (let k = 0; k < opts.length; k++) {
        const n = (from + k) % opts.length;
        if (text(opts[n]).toLowerCase().startsWith(q)) return open(n);
      }
    }
    const typing = () => typed && Date.now() - typedAt < 600 && opts.some(o => text(o).toLowerCase().startsWith(typed + ' '));

    combo.addEventListener('keydown', e => {
      const k = e.key, last = opts.length - 1;
      const printable = k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;
      if (k.length > 1) typed = ''; // arrows, Home, End… end a type-ahead run
      if (!isOpen()) {
        if (k === 'ArrowDown' || k === 'ArrowUp' || k === 'Enter' || k === ' ') { e.preventDefault(); open(e.altKey ? undefined : current()); }
        else if (k === 'Home') { e.preventDefault(); open(0); }
        else if (k === 'End') { e.preventDefault(); open(last); }
        else if (printable) { e.preventDefault(); open(); typeahead(k); }
        return;
      }
      if (k === ' ' && typing()) { e.preventDefault(); return typeahead(k); } // a space inside a name, as in "In review"
      switch (k) {
        case 'ArrowDown': e.preventDefault(); setActive(Math.min(active + 1, last)); break;
        case 'ArrowUp':
          e.preventDefault();
          if (e.altKey) { if (!multi && active > -1) pick(opts[active]); close(); }
          else setActive(Math.max(active - 1, 0));
          break;
        case 'Home': e.preventDefault(); setActive(0); break;
        case 'End': e.preventDefault(); setActive(last); break;
        case 'PageDown': e.preventDefault(); setActive(Math.min(active + 10, last)); break;
        case 'PageUp': e.preventDefault(); setActive(Math.max(active - 10, 0)); break;
        case 'Enter': case ' ':
          e.preventDefault();
          typed = '';
          if (active > -1) pick(opts[active]);
          if (!multi) close();
          break;
        case 'Escape': e.preventDefault(); close(); break;
        case 'Tab': if (!multi && active > -1) pick(opts[active]); close(); break;
        default:
          if (multi && (e.ctrlKey || e.metaKey) && k.toLowerCase() === 'a') { e.preventDefault(); setAll(); render(); changed(); }
          else if (printable) { e.preventDefault(); typeahead(k); }
      }
    });
    combo.addEventListener('click', () => (isOpen() ? close() : open(current())));
    combo.addEventListener('blur', () => { if (isOpen()) close(); });
    label.addEventListener('click', e => { e.preventDefault(); combo.focus(); });
    popup.addEventListener('mousedown', e => e.preventDefault()); // keep focus on the combobox
    list.addEventListener('click', e => {
      const o = e.target.closest('[role="option"]');
      if (!o) return;
      setActive(opts.indexOf(o));
      pick(o);
      if (!multi) close();
      combo.focus();
    });
    list.addEventListener('pointermove', e => {
      const o = e.target.closest('[role="option"]');
      if (o && !o.hasAttribute('data-active')) setActive(opts.indexOf(o));
    });
    document.addEventListener('pointerdown', e => { if (isOpen() && !root.contains(e.target)) close(); });
    if (clear) clear.addEventListener('click', () => {
      if (multi) [...native.options].forEach(n => { n.selected = false; });
      else native.value = '';
      render();
      changed();
      combo.focus();
    });
    if (native.form) native.form.addEventListener('reset', () => setTimeout(render));
    render();
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#9aa6bd] uppercase">Native selects on a dark surface</span><div class="rounded-2xl border border-[#232c3d] bg-[#121826] px-6 pt-[22px] pb-6 shadow-[inset_0_1px_0_rgba(255,255,255,.03)]">
<div class="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-4">
  <div class="grid min-w-0 gap-[7px] text-dk-ink [color-scheme:dark]"><label class="text-[.88rem] font-semibold" for="dk-level">Log level</label>
    <select class="dk-chevron block h-11 w-full cursor-pointer appearance-none rounded-[10px] border border-dk-line bg-dk-field pr-10 pl-[13px] text-[.94rem] text-dk-ink [color-scheme:dark] transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none motion-reduce:transition-none" id="dk-level" name="level"><option>Debug</option><option selected>Info</option><option>Warning</option><option>Error</option></select></div>
  <div class="grid min-w-0 gap-[7px] text-dk-ink [color-scheme:dark]"><label class="text-[.88rem] font-semibold" for="dk-env">Environment</label>
    <select class="dk-chevron block h-11 w-full cursor-pointer appearance-none rounded-[10px] border border-dk-line bg-dk-field pr-10 pl-[13px] text-[.94rem] text-dk-ink [color-scheme:dark] transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none motion-reduce:transition-none" id="dk-env" name="environment"><option selected>Production</option><option>Staging</option><option>Preview</option></select></div>
</div>
</div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#9aa6bd] uppercase">Multiselect with select all and a count</span><div class="rounded-2xl border border-[#232c3d] bg-[#121826] px-6 pt-[22px] pb-6 shadow-[inset_0_1px_0_rgba(255,255,255,.03)]">
<div class="relative grid min-w-0 grid-cols-[minmax(0,1fr)] gap-[7px] text-dk-ink [color-scheme:dark]" data-select data-names="3">
  <label class="text-[.88rem] font-semibold" id="dk-rg-label" for="dk-rg-native">Deploy to regions</label>
  <select class="block p-1.5 w-full cursor-pointer appearance-none rounded-[10px] border border-dk-line bg-dk-field pr-10 pl-[13px] text-[.94rem] text-dk-ink [color-scheme:dark] transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none motion-reduce:transition-none" id="dk-rg-native" name="regions" multiple size="7"><option value="fra-1" selected>Frankfurt</option><option value="sto-1" selected>Stockholm</option><option value="lon-2" selected>London</option><option value="iad-1">Virginia</option><option value="pdx-1">Oregon</option><option value="sin-1">Singapore</option><option value="tyo-1">Tokyo</option></select>
  <div class="relative" data-ui hidden>
    <div class="group flex h-[46px] cursor-pointer items-center gap-2.5 rounded-[10px] border border-dk-line bg-dk-field px-[13px] text-[.94rem] select-none transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none aria-expanded:border-dk-accent aria-expanded:shadow-[0_0_0_4px_rgba(129,140,248,.4)] motion-reduce:transition-none" id="dk-rg-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-rg-list" aria-labelledby="dk-rg-label">
      <svg class="size-[18px] flex-none text-dk-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 3.7 5.7 3.7 9s-1.2 6.3-3.7 9c-2.5-2.7-3.7-5.7-3.7-9S9.5 5.7 12 3Z"/></svg>
      <span class="min-w-0 flex-1 truncate group-data-empty:text-dk-muted" data-shown data-placeholder="No regions" data-all-text="All regions">No regions</span>
      <span class="grid h-[22px] min-w-[22px] flex-none place-items-center rounded-full bg-dk-accent px-[7px] text-[.75rem] font-extrabold text-dk-accent-ink" data-count aria-hidden="true" hidden>0</span>
      <svg class="size-4 flex-none text-dk-muted transition-transform duration-200 group-aria-expanded:rotate-180 motion-reduce:transition-none" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="absolute top-[calc(100%+6px)] right-0 left-0 z-30 rounded-xl border border-[#2e3850] bg-dk-pop p-1.5 shadow-[0_22px_44px_-14px_rgba(0,0,0,.7)]" data-popup hidden>
      <div class="max-h-[272px] overflow-y-auto overscroll-contain outline-none [scrollbar-color:#3a4560_transparent]" role="listbox" id="dk-rg-list" aria-labelledby="dk-rg-label" aria-multiselectable="true" tabindex="-1">
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active relative mb-[7px] font-semibold after:absolute after:inset-x-2.5 after:-bottom-1 after:h-px after:bg-[#2a3346]" role="option" id="dk-rg-all" data-all aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span>Select all</div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-1" data-value="fra-1" data-label="Frankfurt" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Frankfurt <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">fra-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">18 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-2" data-value="sto-1" data-label="Stockholm" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Stockholm <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">sto-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">31 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-3" data-value="lon-2" data-label="London" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">London <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">lon-2</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">24 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-4" data-value="iad-1" data-label="Virginia" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Virginia <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">iad-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">92 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-5" data-value="pdx-1" data-label="Oregon" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Oregon <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">pdx-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">151 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-6" data-value="sin-1" data-label="Singapore" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Singapore <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">sin-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">164 ms</span></div>
        <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-rg-7" data-value="tyo-1" data-label="Tokyo" aria-selected="false"><span class="size-[18px] flex-none rounded-[5px] border-[1.5px] border-[#7d89a6] bg-transparent bg-size-[13px] bg-center bg-no-repeat group-data-chosen:dk-tick group-data-chosen:border-dk-accent group-data-chosen:bg-dk-accent group-data-mixed:dk-dash group-data-mixed:border-dk-accent group-data-mixed:bg-dk-accent" aria-hidden="true"></span><span class="min-w-0 flex-1 font-medium">Tokyo <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">tyo-1</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">218 ms</span></div>
      </div>
    </div>
  </div>
</div>
</div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#9aa6bd] uppercase">Grouped single select with specs and prices</span><div class="rounded-2xl border border-[#232c3d] bg-[#121826] px-6 pt-[22px] pb-6 shadow-[inset_0_1px_0_rgba(255,255,255,.03)]">
<div class="relative grid min-w-0 grid-cols-[minmax(0,1fr)] gap-[7px] text-dk-ink [color-scheme:dark]" data-select>
  <label class="text-[.88rem] font-semibold" id="dk-sz-label" for="dk-sz-native">Instance size</label>
  <select class="block p-1.5 w-full cursor-pointer appearance-none rounded-[10px] border border-dk-line bg-dk-field pr-10 pl-[13px] text-[.94rem] text-dk-ink [color-scheme:dark] transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none motion-reduce:transition-none" id="dk-sz-native" name="size">
    <optgroup label="General purpose"><option value="s1">Starter S1</option><option value="s2" selected>Starter S2</option><option value="b2">Balanced B2</option></optgroup>
    <optgroup label="Compute optimised"><option value="c4">Compute C4</option><option value="c8">Compute C8</option></optgroup>
    <optgroup label="Memory optimised"><option value="m16">Memory M16</option></optgroup>
  </select>
  <div class="relative" data-ui hidden>
    <div class="group flex h-[46px] cursor-pointer items-center gap-2.5 rounded-[10px] border border-dk-line bg-dk-field px-[13px] text-[.94rem] select-none transition-[border-color,box-shadow] duration-150 hover:border-[#8a95b0] focus-visible:border-dk-accent focus-visible:shadow-[0_0_0_4px_rgba(129,140,248,.4)] focus-visible:outline-none aria-expanded:border-dk-accent aria-expanded:shadow-[0_0_0_4px_rgba(129,140,248,.4)] motion-reduce:transition-none pl-2" id="dk-sz-combo" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="dk-sz-list" aria-labelledby="dk-sz-label">
      <span class="min-w-0 flex-1 truncate group-data-empty:text-dk-muted flex items-center gap-2.5 font-semibold [&_[data-pic]]:size-[30px]" data-shown data-placeholder="Choose a size">Choose a size</span>
      <svg class="size-4 flex-none text-dk-muted transition-transform duration-200 group-aria-expanded:rotate-180 motion-reduce:transition-none" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m4 6 4 4 4-4"/></svg>
    </div>
    <div class="absolute top-[calc(100%+6px)] right-0 left-0 z-30 rounded-xl border border-[#2e3850] bg-dk-pop p-1.5 shadow-[0_22px_44px_-14px_rgba(0,0,0,.7)]" data-popup hidden>
      <div class="max-h-[272px] overflow-y-auto overscroll-contain outline-none [scrollbar-color:#3a4560_transparent]" role="listbox" id="dk-sz-list" aria-labelledby="dk-sz-label" tabindex="-1">
        <div role="group" aria-labelledby="dk-sz-g1">
          <div class="px-2.5 pt-[9px] pb-1 text-[.68rem] font-bold tracking-[.1em] text-dk-muted uppercase" role="presentation" id="dk-sz-g1">General purpose</div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-1" data-value="s1" data-label="Starter S1" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="min-w-0 flex-1 font-medium">Starter S1 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">1 vCPU · 2 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.012/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-2" data-value="s2" data-label="Starter S2" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="min-w-0 flex-1 font-medium">Starter S2 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">2 vCPU · 4 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.024/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-3" data-value="b2" data-label="Balanced B2" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#a5b4fc" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></svg></span><span class="min-w-0 flex-1 font-medium">Balanced B2 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">2 vCPU · 8 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.058/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g2">
          <div class="px-2.5 pt-[9px] pb-1 text-[.68rem] font-bold tracking-[.1em] text-dk-muted uppercase" role="presentation" id="dk-sz-g2">Compute optimised</div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-4" data-value="c4" data-label="Compute C4" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="min-w-0 flex-1 font-medium">Compute C4 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">4 vCPU · 8 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.102/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-5" data-value="c8" data-label="Compute C8" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#fcd34d" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></svg></span><span class="min-w-0 flex-1 font-medium">Compute C8 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">8 vCPU · 16 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.204/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
        <div role="group" aria-labelledby="dk-sz-g3">
          <div class="px-2.5 pt-[9px] pb-1 text-[.68rem] font-bold tracking-[.1em] text-dk-muted uppercase" role="presentation" id="dk-sz-g3">Memory optimised</div>
          <div class="group flex cursor-pointer items-center gap-[11px] rounded-lg px-2.5 py-[7px] text-[.92rem] data-active:bg-dk-active" role="option" id="dk-sz-6" data-value="m16" data-label="Memory M16" aria-selected="false"><span class="grid size-8 flex-none place-items-center rounded-lg bg-[#1e2638] text-(--tone) [&_svg]:size-[17px]" style="--tone:#6ee7b7" data-pic aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 11v2M11 11v2M15 11v2M7 17v3M17 17v3"/></svg></span><span class="min-w-0 flex-1 font-medium">Memory M16 <span class="ml-1.5 font-mono text-[.78rem] font-normal text-dk-muted max-[480px]:ml-0 max-[480px]:block">2 vCPU · 16 GB</span></span><span class="flex-none font-mono text-[.78rem] text-dk-muted">$0.091/h</span><svg class="invisible size-4 flex-none text-dk-accent group-data-chosen:visible" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3.5 8.5 3 3 6-7"/></svg></div>
        </div>
      </div>
    </div>
  </div>
</div>
</div></div>
CSS
@theme {
  --color-dk-accent: #818cf8;
  --color-dk-accent-ink: #0b0f17;
  --color-dk-line: #6b7794;
  --color-dk-field: #0f1522;
  --color-dk-pop: #161d2c;
  --color-dk-active: #232c44;
  --color-dk-ink: #e6ebf5;
  --color-dk-muted: #9aa6bd;
}
/* the chevron on the native selects, and the tick / dash inside a chosen checkbox */
@utility dk-chevron { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239aa6bd' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 13px center; background-size: 16px; }
@utility dk-tick { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 8.5 3 3 6-7'/%3E%3C/svg%3E"); }
@utility dk-dash { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230b0f17' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E"); }
JavaScript
// Select-only combobox with a listbox popup (WAI-ARIA APG), single or multi-select.
// Focus stays on the combobox; aria-activedescendant points at the highlighted option.
// Closed: Down, Up, Enter or Space open it, Home / End open at the first / last option, typing opens and jumps.
// Open: Down / Up / Home / End / Page Up / Page Down move; Enter or Space picks (ticks in a multiselect);
// Ctrl+A ticks all; typing jumps to the next match; Escape closes; Tab closes and moves on.
// The <select> inside each [data-select] holds the real form value and the starting selection. It is hidden
// once this script runs, so without JavaScript the native control is still there and still works.
(() => {
  document.querySelectorAll('[data-select]').forEach(root => {
    const native = root.querySelector('select');
    const label = root.querySelector('label');
    const combo = root.querySelector('[role="combobox"]');
    const popup = root.querySelector('[data-popup]');
    const list = root.querySelector('[role="listbox"]');
    const shown = root.querySelector('[data-shown]');
    const count = root.querySelector('[data-count]');
    const clear = root.querySelector('[data-clear]');
    const multi = native.multiple;
    const opts = [...list.querySelectorAll('[role="option"]')];
    const all = list.querySelector('[data-all]');
    const items = opts.filter(o => o !== all);
    const source = o => [...native.options].find(n => n.value === o.dataset.value);
    const text = o => o.dataset.label || o.textContent.trim();
    const isOpen = () => combo.getAttribute('aria-expanded') === 'true';
    const changed = () => native.dispatchEvent(new Event('change', { bubbles: true }));
    let active = -1, typed = '', typedAt = 0;

    native.hidden = true;
    root.querySelector('[data-ui]').hidden = false;

    function render() {
      const on = items.filter(o => source(o).selected);
      items.forEach(o => {
        o.toggleAttribute('data-chosen', on.includes(o));
        if (multi || !isOpen()) o.setAttribute('aria-selected', on.includes(o));
      });
      if (all) {
        all.setAttribute('aria-selected', on.length === items.length);
        all.toggleAttribute('data-chosen', on.length === items.length);
        all.toggleAttribute('data-mixed', on.length > 0 && on.length < items.length);
      }
      if (!on.length) shown.textContent = shown.dataset.placeholder;
      else if (!multi) {
        const pic = on[0].querySelector('[data-pic]');
        shown.replaceChildren(...(pic ? [pic.cloneNode(true)] : []), text(on[0]));
      } else if (on.length === items.length && shown.dataset.allText) shown.textContent = shown.dataset.allText;
      else shown.textContent = on.length <= (+root.dataset.names || 2) ? on.map(text).join(', ') : on.length + ' selected';
      combo.toggleAttribute('data-empty', !on.length);
      if (count) { count.textContent = on.length; count.hidden = !on.length; }
      if (clear) clear.hidden = !on.length;
    }
    function setActive(i) {
      active = i;
      opts.forEach((o, n) => {
        o.toggleAttribute('data-active', n === i);
        if (!multi && i > -1) o.setAttribute('aria-selected', n === i); // single select: selection follows the highlight
      });
      if (i < 0) return combo.removeAttribute('aria-activedescendant');
      combo.setAttribute('aria-activedescendant', opts[i].id);
      const o = opts[i].getBoundingClientRect(), b = list.getBoundingClientRect(); // keep it in view without scrolling the page
      if (i === 0) list.scrollTop = 0;
      else if (o.top < b.top) list.scrollTop -= b.top - o.top;
      else if (o.bottom > b.bottom) list.scrollTop += o.bottom - b.bottom;
    }
    function open(i) {
      if (!isOpen()) { popup.hidden = false; combo.setAttribute('aria-expanded', 'true'); }
      if (i !== undefined) setActive(Math.max(0, Math.min(opts.length - 1, i)));
    }
    function close() {
      popup.hidden = true;
      combo.setAttribute('aria-expanded', 'false');
      setActive(-1);
      render();
    }
    const current = () => Math.max(0, opts.findIndex(o => o.hasAttribute('data-chosen')));
    function setAll() {
      const every = items.every(o => source(o).selected);
      items.forEach(o => { source(o).selected = !every; });
    }
    function pick(o) {
      if (!multi) native.value = o.dataset.value;
      else if (o === all) setAll();
      else source(o).selected = !source(o).selected;
      render();
      changed();
    }
    function typeahead(ch) {
      const now = Date.now();
      typed = (now - typedAt > 600 ? '' : typed) + ch.toLowerCase();
      typedAt = now;
      const same = [...typed].every(c => c === typed[0]); // pressing one letter again cycles through its matches
      const q = same ? typed[0] : typed;
      const from = active < 0 ? 0 : same ? active + 1 : active;
      for (let k = 0; k < opts.length; k++) {
        const n = (from + k) % opts.length;
        if (text(opts[n]).toLowerCase().startsWith(q)) return open(n);
      }
    }
    const typing = () => typed && Date.now() - typedAt < 600 && opts.some(o => text(o).toLowerCase().startsWith(typed + ' '));

    combo.addEventListener('keydown', e => {
      const k = e.key, last = opts.length - 1;
      const printable = k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;
      if (k.length > 1) typed = ''; // arrows, Home, End… end a type-ahead run
      if (!isOpen()) {
        if (k === 'ArrowDown' || k === 'ArrowUp' || k === 'Enter' || k === ' ') { e.preventDefault(); open(e.altKey ? undefined : current()); }
        else if (k === 'Home') { e.preventDefault(); open(0); }
        else if (k === 'End') { e.preventDefault(); open(last); }
        else if (printable) { e.preventDefault(); open(); typeahead(k); }
        return;
      }
      if (k === ' ' && typing()) { e.preventDefault(); return typeahead(k); } // a space inside a name, as in "In review"
      switch (k) {
        case 'ArrowDown': e.preventDefault(); setActive(Math.min(active + 1, last)); break;
        case 'ArrowUp':
          e.preventDefault();
          if (e.altKey) { if (!multi && active > -1) pick(opts[active]); close(); }
          else setActive(Math.max(active - 1, 0));
          break;
        case 'Home': e.preventDefault(); setActive(0); break;
        case 'End': e.preventDefault(); setActive(last); break;
        case 'PageDown': e.preventDefault(); setActive(Math.min(active + 10, last)); break;
        case 'PageUp': e.preventDefault(); setActive(Math.max(active - 10, 0)); break;
        case 'Enter': case ' ':
          e.preventDefault();
          typed = '';
          if (active > -1) pick(opts[active]);
          if (!multi) close();
          break;
        case 'Escape': e.preventDefault(); close(); break;
        case 'Tab': if (!multi && active > -1) pick(opts[active]); close(); break;
        default:
          if (multi && (e.ctrlKey || e.metaKey) && k.toLowerCase() === 'a') { e.preventDefault(); setAll(); render(); changed(); }
          else if (printable) { e.preventDefault(); typeahead(k); }
      }
    });
    combo.addEventListener('click', () => (isOpen() ? close() : open(current())));
    combo.addEventListener('blur', () => { if (isOpen()) close(); });
    label.addEventListener('click', e => { e.preventDefault(); combo.focus(); });
    popup.addEventListener('mousedown', e => e.preventDefault()); // keep focus on the combobox
    list.addEventListener('click', e => {
      const o = e.target.closest('[role="option"]');
      if (!o) return;
      setActive(opts.indexOf(o));
      pick(o);
      if (!multi) close();
      combo.focus();
    });
    list.addEventListener('pointermove', e => {
      const o = e.target.closest('[role="option"]');
      if (o && !o.hasAttribute('data-active')) setActive(opts.indexOf(o));
    });
    document.addEventListener('pointerdown', e => { if (isOpen() && !root.contains(e.target)) close(); });
    if (clear) clear.addEventListener('click', () => {
      if (multi) [...native.options].forEach(n => { n.selected = false; });
      else native.value = '';
      render();
      changed();
      combo.focus();
    });
    if (native.form) native.form.addEventListener('reset', () => setTimeout(render));
    render();
  });
})();

About this set

Selects for dark dashboards and developer tools, where light-theme controls lose their borders. Native selects on a dark surface pairs a log level and an environment select that set color-scheme: dark, so the browser’s own option list opens dark too. Multiselect with select all and a count is a deploy-to-regions picker listing seven regions with their codes and latency, with a Select all row, a count badge and a summary that switches from names to All regions. Grouped single select with specs and prices picks an instance size from three families, each option showing a coloured icon, its vCPU and memory and an hourly price, with the choice copied into the closed field. The two custom menus use the WAI-ARIA select-only combobox pattern with a listbox, so arrow keys, Home, End, Space, Enter, type-ahead and Escape all work, and hidden select elements keep the real values for the form. Borders stay at 3:1 against the card and the focus ring is a light indigo that shows on the dark field.

What’s included

  • color-scheme: dark for native option lists
  • Multi-select with Select all, count badge and summary
  • Grouped single select with specs and prices
  • WAI-ARIA select-only combobox and listbox
  • Bootstrap version on data-bs-theme="dark"

Accessibility

Native selects keep their labels and browser behaviour; the custom menus are role="combobox" fields labelled by their visible labels, with aria-expanded and aria-activedescendant pointing into listboxes that report aria-selected, and the size list uses labelled groups. Text reaches 4.5:1 and field borders 3:1 on the dark card, the focus ring is light indigo, and transitions stop under prefers-reduced-motion.

Customise it

Change --dk-accent, --dk-field, --dk-pop, --dk-active and --dk-line on .dk; in Tailwind edit the --color-dk-* theme colours. The Bootstrap version sets data-bs-theme="dark" on each component, so it follows Bootstrap's dark colour mode.