Composite
Combobox Requires JS
A typeahead-searchable picker. Combines a text input with a filtered listbox so users can either type to filter or arrow-key through options. Use it when you have more than ~15 options and need search, but don't need full free-text input (use <input type="text"> with a datalist for that).
About
Overview
Combobox is a Select with a search box on top. The user can either click the trigger to see all options, or start typing to narrow them down. Each keystroke filters the visible items, arrow keys jump between them, Enter picks one. The chosen value is submitted via a hidden input named after the name attribute.
Combobox vs Select vs native select. Use native for short lists where the platform dropdown is fine. Use Select for richer item rendering but no search. Use Combobox when there are too many items to scroll comfortably and the user knows roughly what they want.
Parts
Anatomy
Assembled
<ren-combobox> wrapper. Manages selection, filtering, and the hidden form value.
<input role="combobox">. Holds the search query when open, the chosen label when closed.
role="listbox" popover with the visible (filtered) items. Hides items that don't match the query.
.ren-combobox-item with role="option" and a data-value. Filtering matches against the visible text.
Live
Demo
<ren-combobox name="country" placeholder="Search a country…">
<div class="ren-combobox-item" data-value="ar">Argentina</div>
<div class="ren-combobox-item" data-value="br">Brazil</div>
<div class="ren-combobox-item" data-value="cl">Chile</div>
…
</ren-combobox>
Shapes
Variants
Items can hold structured content — a primary line and a muted secondary line. Useful for "User name + email" or "Repo name + organization" patterns.
Wrap related items in .ren-combobox-group with an optional .ren-combobox-group-label. Filtering keeps the group label visible while any of its items match.
Listen for ren-search and replace the items as the user types. Show .ren-combobox-loading while a request is in flight.
Reference
API
CSS classes
| Class | Effect |
|---|---|
.ren-combobox | Wrapper. Sets up positioning context for the floating list. |
.ren-combobox-input | The text input. role="combobox", aria-autocomplete="list". |
.ren-combobox-list | The listbox popover. Shows below the input on open. |
.ren-combobox-item | One option. Use with role="option" and data-value. |
.ren-combobox-group / -group-label | Optional grouping for items. |
.ren-combobox-empty | "No results" message shown when the filter matches nothing. |
.ren-combobox-loading | Loading row for async data. |
Web Component attributes
| Attribute | Type | Default | Notes |
|---|---|---|---|
name | string | — | Form field name. Submitted via a hidden input. |
value | string | — | Initial selected data-value. |
placeholder | string | "Search..." | Empty-state text in the input. |
placement | "top" | "bottom" | "bottom" | Preferred side for the local list. Reflected to data-side; top opens above the input. |
disabled | boolean | false | Locks the input. |
async | boolean | false | If present, suppresses local filtering — the parent app provides items via ren-search. |
Events
| Event | Detail |
|---|---|
ren-change | Selection changed. { value, item }. Bubbles, composed. |
ren-search | User typed. { query }. Use this for async loading. Bubbles. |
ren-open / ren-close | Listbox opened / closed. Bubbles, composed. |
JavaScript methods
| Method | Returns | Args | Notes |
|---|---|---|---|
.value |
string | — | Getter / setter for the selected data-value. Setting it updates the input and dispatches ren-change. |
.open() |
void | — | Programmatically opens the listbox. |
.close() |
void | — | Programmatically closes the listbox. |
.setLoading(state) |
void | boolean | Toggles the .ren-combobox-loading row. Pair with ren-search to indicate an async fetch is in flight. |
.setItems(items) |
void | Array<{value, label, description?}> | Replaces the visible options. Use with the async attribute when the parent app owns filtering. |
Inclusive by default
Accessibility
Keyboard
Labelling
Wrap inside Form Field, or set aria-label on the input. Don't use the placeholder as a label.
Always handle the empty state. When filtering yields zero items, show .ren-combobox-empty. Silence is worse than "No results — try a different word".
Patterns
Examples
Async loading
const combo = document.querySelector('ren-combobox[async]');
combo.addEventListener('ren-search', async (e) => {
combo.setLoading(true);
const results = await fetch(`/api/users?q=${encodeURIComponent(e.detail.query)}`).then(r => r.json());
combo.setItems(results.map(u => ({ value: u.id, label: u.name, description: u.email })));
combo.setLoading(false);
});
Pre-selecting
<ren-combobox name="country" value="ar">
<div class="ren-combobox-item" data-value="ar">Argentina</div>
<div class="ren-combobox-item" data-value="br">Brazil</div>
</ren-combobox>