Autocomplete
A free-text input with suggestions - the typed value doesn't have to match an option, built on the same Combobox foundation as Select.
Usage
Suggestions accept string/number arrays or option objects. A primitive is both
its label and value. Objects display label by default; their value field is
used when present, and objects without it are themselves the model value. Set
labelKey or valueKey to choose other fields. Existing suggestions preserve
numeric 1 versus string "1". Newly created free text is always a string;
numeric suggestions do not cause typed text to be coerced. Use forceSelection
to disallow creating values.
Autocomplete infers models and update events from its suggestion identity field.
Numeric suggestions allow number | string | undefined in single mode and
(number | string)[] in multiple mode. A literal forceSelection narrows those
types to the suggestion identity; a dynamic boolean retains the string union.
Readonly option/group arrays and custom top-level valueKey/labelKey fields
are supported. Every suggestion identity must be unique across the whole list,
including disabled suggestions and suggestions in different groups; a duplicate
throws when Autocomplete reads the options. Numeric 1 and string '1' remain
distinct. Declare the item type for initially empty async arrays.
The exported types use an option entry type: AutocompleteProps<number>
describes numeric primitive items, while
AutocompleteProps<{ value: number, label: string }> describes option records.
The parameters are entry, identity key, multiple mode and forced mode.
Author suggestions as a readonly Row[], or as readonly (Row | SelectGroup<Row>)[]
when mixing suggestions and groups; there are no separate SelectItems,
SelectOption, or SelectOptionGroup authoring aliases. A wrapper that needs
the exact validated items type can use AutocompleteProps<Row, 'id'>['items']. For example,
AutocompleteProps<Row, 'id', true, true> describes forced multiple selection.
AutocompleteEmits uses the same parameters. Render functions can specialize
Autocomplete<Row, 'id', true, true> directly; templates normally infer it.
Import public helpers from @sewadah/selaras/types; derive slot types through
AutocompleteSlots.
forceSelection blocks new unmatched text. It does not erase existing values
when toggled dynamically or require that a selected async identity already be
in the current options. Native reset restores the captured default, including
strings created before a dynamic switch. Idle inputs refresh their selected
label when options load; active queries and parent-controlled searchTerm
retain their text.
defaultValue initializes uncontrolled selection and is the native form reset
target. modelValue remains parent-controlled. With name, each selected value
submits as a field; primitives are strings and objects are JSON. Empty or
disabled selections submit none. form may reference an external form ID.
Unlike Select, whatever the user types can become the value even if it doesn't match a suggestion - picking a suggestion is a shortcut, not a requirement.
<script setup lang="ts">
const value = ref('')
const fruitItems = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
]
</script>
<template>
<SAutocomplete v-model="value" placeholder="Type anything" :items="fruitItems" />
</template>
The input itself is always the trigger - typing works immediately, with no
separate "open" step, since there's no equivalent of Select's
searchable: false mode here.
Dropdown
There's no built-in "browse everything" button by default - typing is the
whole interaction. dropdown adds one anyway: a trailing chevron that opens
the popover and clears whatever's typed, so it always shows the full list
rather than staying scoped to the current filter.
<SAutocomplete v-model="value" dropdown placeholder="Type or browse" :items="fruitItems" />
Multiple values
multiple uses a comma-separated display by default, with the editable query
beside the selected labels. displayMode="chip" shows removable chips instead.
Both modes offer a Create option for text that isn't already in items. Press
Enter to add it, or move to an existing suggestion first. Once the query
clears, the open list shows all available options again. Choosing an already
selected option leaves the selection in place; chip mode also offers a remove
button on each selected value. In comma mode, press Backspace with an empty
query to remove the last selected value.
<SAutocomplete v-model="fruits" multiple :items="fruitItems" />
<SAutocomplete v-model="tags" multiple display-mode="chip" :items="fruitItems" />
Changing selection mode
For an uncontrolled Autocomplete, changing multiple converts its local
selection once: single value becomes [value], single undefined becomes
[], a multiple selection becomes its first value, and an empty array becomes
undefined. Switching to single mode drops later active selections. That
conversion emits one update:modelValue event. The captured
defaultValue does not change after mount; reset projects it into the current
mode. For example, defaultValue: [0, 1] resets to 0 in single mode and to
[0, 1] after changing back to multiple mode.
A controlled parent must change modelValue and multiple in the same render:
an array when multiple is true, and a scalar or undefined when it is false.
undefined is allowed as the empty multiple value. A scalar value in
multiple mode, or any array in single mode, throws a descriptive error.
Each value in a multiple modelValue or defaultValue array must also be
unique, including created or unresolved async identities. Numeric 1 and
string '1' are distinct values.
Autocomplete does not convert controlled values or emit an update solely
because the mode changed. Changing modes also preserves an active query, open
popover, and focus when the input was focused. It does not take focus from a
mode button or another control. A parent-controlled searchTerm retains its
text; idle input labels follow resetSearchTermOnSelect.
Changing mode during IME composition cancels editing and requests that the
query and popup be cleared. Complete composition before changing mode to retain
the query.
Clear
clearable works the same as Select's - a
button that resets the selection once there's something to clear. Clearing
also blanks the displayed text back to the placeholder, the same way picking
a suggestion fills it in.
<SAutocomplete v-model="value" clearable placeholder="Type anything" :items="fruitItems" />
Force selection
This is the one real behavioral difference from Select, not just a shared
prop: forceSelection reverts typed text that doesn't match any option back
to blank on blur or Enter, instead of accepting it as a new value. It exists
for consumers who want Autocomplete's UX (input-as-trigger, inline filtering)
without actually allowing arbitrary values - if that's the goal from the
start, Select does the same job more directly.
In multiple mode, typing bana shows Create "bana" before the matching
Banana suggestion. Enter chooses the active option, so you can
create bana immediately or press ArrowDown then Enter
to select Banana. Clicking either option works too. In single mode, the list
shows matching suggestions without a Create row: Enter selects the active
suggestion, while unmatched text is accepted as free text on Enter or blur.
When no suggestions match in single mode, the popup hides. Tab moves
focus normally. forceSelection hides the Create option and rejects unmatched
text. An idle Enter does not remove an existing chip and can submit
a parent form normally.
<SAutocomplete v-model="value" force-selection :items="fruitItems" />
Arrow
arrow works the same as Select's: use
true for the default pointer or an object to configure its size, rounded tip,
and corner clearance.
<SAutocomplete v-model="value" arrow :items="fruitItems" />
<SAutocomplete v-model="value" :arrow="{ width: 16, height: 8, rounded: true, padding: 12 }" :items="fruitItems" />
Custom option rendering, objects, and groups
The item/group slots and labelKey/valueKey mapping all work
identically to Select's - see
Custom option rendering,
Custom objects, and
Grouped options.
Created text and async selections can be absent from items. Their chips use
the selected text until a complete option exists; the item slot receives only
real option records. Autocomplete's selection display is its editable input;
it has no value slot.
Virtualization
Also identical to Select's -
virtualize renders only the visible rows, grouped items flatten while
virtualized, and it windows what's already in items rather than
lazy-loading more of it.
Search text
Since the input here IS the trigger, its text doubles as both the filter and
the display of the current value: single-select shows the picked option's
label after you choose one, multi-select clears after each commit (the chips
show the selection instead). Same v-model:search-term and
resetSearchTermOnBlur/resetSearchTermOnSelect escape hatches as
Select apply here too.
Sizes and states
size, loading, disabled, invalid, and color all behave the same as
Select's - see the Props table
below.
Adaptive presentation
adaptive uses the same breakpoint and open-time latch as
Select. On small screens, the
field opens a modal containing an input and suggestions. Single selection closes
the modal; multiple selection and free-text entry also provide a Done button.
The modal shares the field’s query and selection and restores focus when closed:
Forms integration
Also the same as Select's -
wrap it in FormField for id/name/invalid
and aria-describedby wired up automatically.
Accessibility
Autocomplete renders the same Reka UI Combobox primitive as
Select, so the same ARIA combobox
semantics apply - role="combobox" with aria-expanded/aria-controls on
the input, role="option" per suggestion, arrow keys to move the highlight,
Enter to select. The one addition here is aria-autocomplete="list",
since (unlike Select) the trigger is a real editable text field with live
suggestions as you type.
Custom :ui
Autocomplete renders the same internal combobox base as Select, so they share one theme file:
Global and scoped overrides use the ui.select key for both components.
Custom leading and trailing content
Use #leading and #trailing for text, icons, or buttons inside the field frame.
The slots receive { disabled, size, buttonSize }; buttonSize fits a Button
inside the current field size. Give interactive controls an accessible label,
use type="button", and pass disabled through to match the field state.
Custom content stays visible alongside the built-in clear button, loading indicator, and dropdown control.
The field reserves space for the rendered content, including suffixes whose
width changes. Use ui.leading and ui.trailing to customize its wrappers.
Custom controls render outside the trigger button, so they can receive keyboard
focus without creating nested buttons or opening the options list on click.
With adaptive, these slots customize the original field; the modal retains
its dedicated search and selection controls.
Props
Same as Select, minus searchable (always
on), plus these Autocomplete-only additions:
positioning and portal follow Select's anchored panel contract.
| Prop | Type | Default |
|---|---|---|
dropdown | boolean | false |
forceSelection | boolean | false |
Slots
Autocomplete supports leading, trailing, item, group, header, footer, empty,
empty-filter, clear-icon, loading-icon and dropdown-icon. Its input
displays the selected label directly. Select's value and filter-icon slots
are specific to Select's trigger display and separate filter field.
leading and trailing receive { disabled, size, buttonSize }, as described above.