Autocomplete

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless autocomplete
Source View Source

Overview of the Phoenix and LiveView Headless Autocomplete Component

The Mishka Chelekom Headless Autocomplete ships zero opinionated styling - only the semantic markup, full WAI-ARIA combobox wiring, typeahead filtering, a roving highlight, and paired-presence state (data-open/data-closed/data-highlighted/data-hidden). Every part - input, clear, popup, group and item - is addressable through a *_class attribute, so you style it entirely with your own Tailwind classes while the accessibility and behavior keep working underneath.

The root takes class, input_class, clear_class, popup_class, status_class, empty_class, item_class, group_label_class and group_list_class. The last three reach every item, group label and group list, so the options themselves need no classes. An option slot can still add its own class to one item, and the first option of a group takes group_class, group_label_class and group_list_class for that group only.

Base UI Autocomplete example (Phoenix headless)

Base UI Autocomplete example (Phoenix headless)

heex
<label class="flex flex-col gap-1 text-sm font-bold text-neutral-950 dark:text-white">
  Search tags
  <.autocomplete
    id="baseui-autocomplete-hero"
    placeholder="e.g. feature"
    input_class="h-8 w-[16rem] border border-neutral-950 bg-white dark:bg-neutral-950 px-2 text-sm font-normal text-neutral-950 placeholder:text-neutral-500 dark:placeholder:text-neutral-400 focus:outline-2 focus:-outline-offset-1 focus:outline-neutral-950 dark:focus:outline-white dark:border-white dark:text-white"
    popup_class="top-full mt-1 font-normal w-[16rem] max-w-[16rem] border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0] shadow-black/12 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none outline-0 overflow-y-auto scroll-py-[0.25rem] py-1 overscroll-contain max-h-[22.5rem]"
    item_class="flex cursor-default items-center gap-2 py-2 pr-2 pl-2 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-0 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white"
  >
    <:option value="feature">feature</:option>
    <:option value="bug">bug</:option>
    <:option value="docs">docs</:option>
    <:option value="design">design</:option>
    <:option value="urgent">urgent</:option>
    <:empty>
      <div class="py-4 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400">
        No tags found.
      </div>
    </:empty>
  </.autocomplete>
</label>

Base UI Autocomplete example - groups and a clear button (Phoenix headless)

Consecutive options with the same group sit under one label. group_label_class and group_list_class on the root style every label and every list, so the options only repeat value and group. clear adds a button that empties the field. The engine sets data-hidden on it while the field is empty, so clear_class carries data-[hidden]:hidden, and the button stays out of sight until you type.

Base UI Autocomplete example - groups and a clear button (Phoenix headless)

heex
<label class="flex flex-col gap-1 text-sm font-bold text-neutral-950 dark:text-white">
  Select a tag
  <.autocomplete
    id="grouped-autocomplete"
    clear
    placeholder="e.g. feature"
    class="w-64 max-w-full"
    input_class="h-8 w-full border border-neutral-950 bg-white pr-8 pl-2 text-sm font-normal text-neutral-950 placeholder:text-neutral-500 focus:outline-2 focus:-outline-offset-1 focus:outline-neutral-950 dark:border-white dark:bg-neutral-950 dark:text-white dark:placeholder:text-neutral-400 dark:focus:outline-white"
    clear_class="absolute top-0 right-0 flex size-8 items-center justify-center border-0 bg-transparent p-0 text-lg leading-none text-neutral-600 hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-neutral-950 data-[hidden]:hidden dark:text-neutral-400 dark:hover:text-white dark:focus-visible:outline-white"
    popup_class="top-full mt-1 font-normal w-full border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0] shadow-black/12 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none overflow-y-auto overscroll-contain py-1 max-h-[22.5rem]"
    item_class="flex cursor-default items-center gap-2 py-2 pr-2 pl-2 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-0 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white"
    group_label_class="p-2 text-sm leading-4 text-neutral-500 select-none dark:text-neutral-400"
    group_list_class="pb-1"
  >
    <:option value="feature" group="Type">feature</:option>
    <:option value="bug" group="Type">bug</:option>
    <:option value="docs" group="Area">docs</:option>
    <:option value="design" group="Area">design</:option>
    <:option value="urgent" group="Priority">urgent</:option>
    <:empty>
      <div class="py-4 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400">
        No tags found.
      </div>
    </:empty>
  </.autocomplete>
</label>

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-autocomplete__* class you can target, plus paired-presence state for filtering, highlighting, and open/closed transitions.

Partdata-partState / ARIAPurpose
Inputinputrole="combobox", aria-controls, aria-expanded, aria-autocomplete, aria-activedescendantThe text field that filters options as you type. Takes input_class.
Clearcleararia-label, data-hiddenOpt-in button (clear) that empties the input; hidden while it is already empty. Takes clear_class.
Valuevalue-Hidden form input holding the currently selected value.
Statusstatusrole="status", aria-live="polite"Live region announcing the result count as you type. Takes status_class.
Popuppopuprole="listbox", data-open, data-closedThe floating (or inline) list of options. Takes popup_class.
Group / Group labelgroup / group-labelrole="group", aria-labelledby, data-hiddenWraps consecutive same-group options under a labeled group; hidden when every item inside is filtered out. The label takes group_label_class.
Group listgroup-listrole="presentation"The list of options inside a group. Takes group_list_class.
Itemitemrole="option", data-value, data-highlighted, data-hidden, aria-selectedAn individual option; hidden when it does not match the query. Takes item_class on the root and class on an option slot.
Emptyemptydata-hiddenShown when the query matches no options. Takes empty_class.

Accessibility

Follows the WAI-ARIA Combobox pattern: ArrowDown/ArrowUp opens the popup and moves a roving highlight, Enter selects the highlighted option, Escape closes the popup, and typing filters the list (auto_highlight highlights the first match so Enter selects it without arrowing down). The live status region announces the result count to screen readers on every keystroke.