Skip to content

Combobox

Pair .re-combobox with .re-input on a text input linked to a <datalist> via the list attribute. Filtering, keyboard navigation, and the suggestion popup are all provided by the browser — .re-combobox adds the same chevron as .re-select and suppresses the browser’s own picker indicator, so the field looks like a select in every state.

The suggestion popup itself is native browser UI: its width, alignment, and theme follow the browser, not your CSS — Chrome sizes it to the suggestion text rather than the input. If you need a popup that matches the input’s geometry and styling, that’s a custom listbox, and free-form suggestions are usually not worth trading for the native filtering, keyboard, and screen reader behavior you’d have to rebuild.

Live example
<label class="re-field">
  <span class="re-field__label">Favorite fruit</span>
  <input
    type="text"
    class="re-input re-combobox"
    list="fruit-options"
    placeholder="Start typing…"
  />
</label>
<datalist id="fruit-options">
  <option value="Apple"></option>
  <option value="Banana"></option>
  <option value="Cherry"></option>
  <option value="Mango"></option>
  <option value="Peach"></option>
  <option value="Pear"></option>
</datalist>

data-size works the same as on .re-input.

Live example
<label class="re-field">
  <span class="re-field__label">Small</span>
  <input type="text" class="re-input re-combobox" data-size="sm" list="city-options" />
</label>
<label class="re-field">
  <span class="re-field__label">Large</span>
  <input type="text" class="re-input re-combobox" data-size="lg" list="city-options" />
</label>
<datalist id="city-options">
  <option value="Budapest"></option>
  <option value="Berlin"></option>
  <option value="Lisbon"></option>
</datalist>

The native popup’s geometry can’t be controlled — Chrome sizes it to the suggestion text, not the input. If you need a list that is never narrower than the input (and styled to match your theme), opt into the enhanceCombobox behavior by adding data-re-combobox:

import { enhanceCombobox } from "@relements/core";
enhanceCombobox(document);

The same <datalist> stays the data source, so the markup works natively without JavaScript (and in engines without the Popover API, where the enhancement steps aside). Enhancing swaps the native popup for a styled role="listbox": typing filters case-insensitively, Arrow keys move the highlight, Enter commits, Escape closes; focus never leaves the input (aria-activedescendant). Committing sets the value and fires input and change, so frameworks see it like typing.

Live example
<label class="re-field">
  <span class="re-field__label">Time zone</span>
  <input
    type="text"
    class="re-input re-combobox"
    list="zone-options"
    data-re-combobox
    placeholder="Type to filter…"
  />
</label>
<datalist id="zone-options">
  <option value="Europe/Budapest"></option>
  <option value="Europe/Berlin"></option>
  <option value="Europe/Lisbon"></option>
  <option value="America/Argentina/Buenos_Aires"></option>
  <option value="America/New_York"></option>
  <option value="Asia/Tokyo"></option>
  <option value="Australia/Lord_Howe"></option>
</datalist>

The base combobox is a native <input> linked to a <datalist>, so filtering, the suggestion popup, and its keyboard model are the browser’s — assistive tech announces the field, its label, and the native suggestions without any added role or aria-*. The notes below split the native base from the optional enhanceCombobox overlay.

  • Keyboard — base: native. Tab moves focus in and out, typing filters the datalist, and the browser’s own keys open and navigate the suggestion popup. Enhanced: the input becomes an editable combobox — typing filters (case-insensitive substring) and opens the list, ArrowDown/ArrowUp open it (if closed) and move the highlight (wrapping at the ends), Enter commits the highlighted option, Escape closes the list, and Tab closes it and moves on. Committing sets the value and fires input and change, so frameworks observe it like typing.
  • Focus — focus never leaves the input. In the enhanced mode the highlighted option is tracked with aria-activedescendant (no roving focus on the list). The input shows the .re-input :focus-visible ring (--re-shadow-focus, border recoloured to --re-color-focus-ring); set aria-invalid="true" to recolour the border to --re-color-danger-border, announced as invalid.
  • Semantics — wrap the input in <label class="re-field"> with a .re-field__label span (as the demos do) so the label is associated by containment. Enhancing sets role="combobox", aria-autocomplete="list", aria-expanded, and aria-controls on the input, renders a role="listbox" with role="option" children, and marks the active option aria-selected="true" — the editable-combobox pattern. The chevron is a CSS background image and the browser’s native picker indicator is suppressed, so neither is announced.
  • Notes — because the active option has no real DOM focus, the aria-selected highlight (--re-color-bg-muted) flattens under forced colors; the active option is re-cued with the system Highlight/HighlightText colors so the selection survives Windows High Contrast Mode. Without JavaScript — or in engines without the Popover API — the enhancement steps aside and the fully operable native datalist remains. See the field and select pages and the accessibility guide.