A searchable select (combobox) built with Tailwind CSS and vanilla JavaScript, shown as a dark "Choose your stack" form where users filter a list of frameworks by typing. Use it instead of a native select when the list is long enough that scrolling becomes tedious: countries, time zones, team members, tags or product variants.
How it works
The root is .tss-select. Inside the form, [data-tss-combo] wraps a text input with role="combobox", a clear button (data-tss-clear), a chevron toggle (data-tss-toggle) and the floating .tss-panel. The panel holds the #tss-listbox list with role="option" items and a "no results" message marked data-tss-empty. Each option has a data-value, a color dot, the visible name in .tss-text, a short hint and a check icon that appears through the group-aria-selected:opacity-100 utility.
On DOMContentLoaded the script initializes every .tss-select, gives each option a unique id and stores its label. Typing fires the input event: options that do not contain the query get the hidden attribute, the matching part of each label is wrapped in a mark element, and the first match becomes the active option (data-active, styled with data-active:bg-cyan-400/15). The input points to it with aria-activedescendant, so focus never leaves the text field. Choosing an option writes the label into the input, the value into the hidden name="framework" field (data-tss-value) and a line into data-tss-summary. Leaving the field without choosing restores the last committed value. style.css rotates .tss-chevron and adds the tss-drop animation.
Customization
- Add or remove
li role="option"items in#tss-listbox; give each adata-valueand a.tss-textlabel and the filter picks them up. - Set
aria-selected="true"on one option to preselect it on page load (SvelteKit in the demo). name="framework"on thedata-tss-valueinput: rename it to match your backend field.cyanutilities (focus-within:border-cyan-400,bg-cyan-400on the submit button,text-cyan-400on the check icon) andcolor: #22d3eeonmarkinstyle.css: switch to another color to rebrand.max-h-64on#tss-listbox: show more or fewer rows before the list scrolls.bg-slate-950on.tss-selectandbg-slate-900on.tss-panel: swap forbg-whiteandbg-whitewithtext-slate-900for a light version.
Accessibility
The field follows the ARIA combobox pattern: role="combobox", aria-autocomplete="list", aria-controls, a live aria-expanded state and aria-activedescendant. The listbox is labelled by the visible label, options expose aria-selected, and a visually hidden role="status" region announces result counts and selections. Keyboard support: ArrowDown and ArrowUp open the list and move through matches, Enter selects, Escape closes (or clears the value when already closed), Tab closes without changing the value. The clear button has an sr-only label, the toggle is mouse-only and hidden from assistive technology, and the drop animation is disabled under prefers-reduced-motion.
Usage notes
The form submit is simulated; replace the handler in script.js with a fetch() call when you connect a backend. For several instances, repeat the .tss-select block with unique id values for the label, input and listbox.