Combobox with search, free-solo input, multiple selection, and async data.
Introduction
Autocomplete is a combobox built on the same foundation as Select—FormField chrome, Menu listbox, chips, async data, and composed list children.
Use Autocomplete when users should type to filter and optionally commit custom values. Use Select when the value must come from a fixed list and search is optional.
| Prop | Select default | Autocomplete default |
|---|---|---|
| searchable | false | true |
| freeSolo | — | true |
Import
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";Basic usage
Pass an options array of objects with label and value keys. Search is enabled by default. For an initial value without binding, use defaultValue in React or default-value in Vue.
<script setup lang="ts">
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";
const countries = [
{ value: "pt", label: "Portugal" },
{ value: "br", label: "Brazil" },
{ value: "es", label: "Spain" },
{ value: "fr", label: "France" },
];
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
label="Country"
:options="countries"
placeholder="Choose a country"
/>
</div>
</template>
import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";
const countries = [
{ value: "pt", label: "Portugal" },
{ value: "br", label: "Brazil" },
{ value: "es", label: "Spain" },
{ value: "fr", label: "France" },
];
export default function AutocompleteBasic() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
label="Country"
options={countries}
placeholder="Choose a country"
/>
</div>
);
}
Free solo
Enabled by default. Type a value that is not in options and commit with Enter, Tab, or by closing the menu. Set freeSolo={false} (React) or :free-solo="false" (Vue) to require picking from the list.
<script setup lang="ts">
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";
const tags = [
{ value: "design", label: "Design" },
{ value: "engineering", label: "Engineering" },
{ value: "product", label: "Product" },
];
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
label="Tag"
:options="tags"
placeholder="Pick or type a tag"
description="freeSolo is on by default — commit custom text with Enter, Tab, or by closing the menu."
/>
</div>
</template>
import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";
const tags = [
{ value: "design", label: "Design" },
{ value: "engineering", label: "Engineering" },
{ value: "product", label: "Product" },
];
export default function AutocompleteFreeSolo() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
label="Tag"
options={tags}
placeholder="Pick or type a tag"
description="freeSolo is on by default — commit custom text with Enter, Tab, or by closing the menu."
/>
</div>
);
}
Multiple
Enable multiple to allow selecting more than one option. Selected values render as chips.
<script setup lang="ts">
import { ref } from "vue";
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";
const frameworks = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Svelte", value: "svelte" },
{ label: "Angular", value: "angular" },
];
const selected = ref(["react"]);
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
multiple
v-model="selected"
label="Frameworks"
:options="frameworks"
/>
</div>
</template>
import { useState } from "react";
import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";
const frameworks = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Svelte", value: "svelte" },
{ label: "Angular", value: "angular" },
];
export default function AutocompleteMultiple() {
const [selected, setSelected] = useState<string[]>(["react"]);
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
multiple
value={selected}
label="Frameworks"
options={frameworks}
onChange={setSelected}
/>
</div>
);
}
Async data
Pass asyncData with search and resolve callbacks for remote or debounced option lists. Implies searchable.
<script setup lang="ts">
import type { SelectOptionData, SelectValue } from "@bridge-ui/core";
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";
import { ref } from "vue";
const value = ref<null | SelectValue>(null);
const allCities = Array.from({ length: 15 }, (_, index) => ({
label: `City ${index + 1}`,
value: `city-${index + 1}`,
}));
const asyncSearch = async (query: string): Promise<SelectOptionData[]> => {
await new Promise((resolve) => setTimeout(resolve, 400));
const normalized = query.trim().toLowerCase();
if (!normalized) return allCities.slice(0, 8);
return allCities.filter((city) =>
city.label.toLowerCase().includes(normalized),
);
};
const asyncResolve = async (
values: SelectValue[],
): Promise<SelectOptionData[]> =>
values.map(
(v) =>
allCities.find((c) => c.value === v) ?? { value: v, label: String(v) },
);
</script>
<template>
<div class="w-full max-w-sm">
<Autocomplete
v-model="value"
label="City (async)"
placeholder="Type to search cities..."
:async-data="{ search: asyncSearch, resolve: asyncResolve }"
description="Client-side asyncData with search and resolve callbacks."
/>
</div>
</template>
import { useState } from "react";
import type { SelectOptionData, SelectValue } from "@bridge-ui/core";
import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";
const allCities = Array.from({ length: 15 }, (_, index) => ({
label: `City ${index + 1}`,
value: `city-${index + 1}`,
}));
const asyncSearch = async (query: string): Promise<SelectOptionData[]> => {
await new Promise((resolve) => setTimeout(resolve, 400));
const normalized = query.trim().toLowerCase();
if (!normalized) return allCities.slice(0, 8);
return allCities.filter((city) =>
city.label.toLowerCase().includes(normalized),
);
};
const asyncResolve = async (
values: SelectValue[],
): Promise<SelectOptionData[]> =>
values.map(
(v) =>
allCities.find((c) => c.value === v) ?? { value: v, label: String(v) },
);
export default function AutocompleteAsyncData() {
const [value, setValue] = useState<null | SelectValue>(null);
return (
<div className="w-full max-w-sm">
<Autocomplete
value={value}
onChange={setValue}
label="City (async)"
placeholder="Type to search cities..."
asyncData={{ search: asyncSearch, resolve: asyncResolve }}
description="Client-side asyncData with search and resolve callbacks."
/>
</div>
);
}
Validation
Set error and errorMessage to show invalid styling and an error message below the field.
<script setup lang="ts">
import { Autocomplete } from "@bridge-ui/vue/Components/Autocomplete";
const countries = [
{ value: "pt", label: "Portugal" },
{ value: "br", label: "Brazil" },
{ value: "es", label: "Spain" },
];
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
error
label="Country"
:options="countries"
error-message="Please select a valid country."
/>
</div>
</template>
import { Autocomplete } from "@bridge-ui/react/Components/Autocomplete";
const countries = [
{ value: "pt", label: "Portugal" },
{ value: "br", label: "Brazil" },
{ value: "es", label: "Spain" },
];
export default function AutocompleteValidation() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Autocomplete
error
label="Country"
options={countries}
errorMessage="Please select a valid country."
/>
</div>
);
}
Related components
Accessibility
Autocomplete follows the FormField accessibility pattern and WAI-ARIA combobox behavior for the searchable trigger.
- Label, helper text, and error message are linked via controlId and aria-describedby.
- When error is true, the trigger receives aria-invalid="true".
Anatomy
FormField (root)
├── Header — label, optional corner text, required indicator
├── Container — variant shell
│ └── Trigger — searchable input / chips showing the current selection
├── Options panel — filterable listbox (supports loading and free-solo commit)
└── Footer — description or error messageAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| v-model | SelectModel | — | Two-way binding for the selected value (single or multiple). |
| default-value | SelectModel | — | Initial value for uncontrolled usage (without v-model). |
| Prop | Type | Default | Description |
|---|---|---|---|
| value | SelectModel | — | Selected value (single or multiple). Use with onChange for controlled state. |
| onChange | (value: SelectModel) => void | — | Called when the selection changes. |
Autocomplete-specific
| Prop | Type | Default | Description |
|---|---|---|---|
| options | ListboxOptionsInput | — | Options to display. May include section groups ({ title, options, sticky? }) mixed with flat options. |
| children | ReactNode | — | Composed dropdown content (ListSection / ListItem with value). Replaces mapped options. |
| placeholder | string | — | Placeholder when no value is selected. |
| multiple | boolean | false | Whether multiple values can be selected. |
| searchable | boolean | true | Whether options can be filtered via the trigger input. |
| freeSolo | boolean | true | Allows committing typed text that is not in options (Enter, Tab, or closing the menu). |
| clearable | boolean | true | Whether the value can be cleared. |
| asyncData | SelectAsyncData | — | Remote data source. Implies searchable. |
| loading | boolean | — | External or async loading state (OR’d with async in-flight). |
| loadingMessage | string | "Loading..." | Message shown in the dropdown while loading. |
| defaultValue | SelectModel | null | — | Initial value when uncontrolled. |
| optionLabel | string | "label" | Key used to read the label from option objects. |
| optionValue | string | "value" | Key used to read the value from option objects. |
| optionDescription | string | "description" | Key used to read the description from option objects. |
| minItemsForSearch | number | 11 | Minimum option count before search UI is enabled. |
| emptyMessage | string | "No options" | Message when the filtered list is empty. |
| hideEmptyMessage | boolean | false | Hides the empty-state message. |
| flipOptions | boolean | false | Inverts the visual order of options. |
| maxHeight | string | "max-h-60" | Tailwind max-height class for the dropdown options area. |
| disableMaxHeight | boolean | false | When true, the dropdown options list is not height-limited. |
Events
| Event | Payload | Description | | ———– | ––––––––––– | ——————————————————— | | @clear | — | Called when the value is cleared. | | @close | — | Called when the menu closes. | | @open | — | Called when the menu opens. | | @search | query: string | Called when the search query changes. | | @select | option: SelectOption | Called when an option is selected. | | @deselect | option: SelectOption | Called when an option is deselected (multiple mode). | | @change | value: SelectModel | Called when the value changes (alternative to v-model). |
| Prop | Payload | Description | | ———— | ––––––––––– |
| | onClear | — | Called when the value is cleared. | | onClose | — | Called when the menu closes. | | onOpen | — | Called when the menu opens. | | onSearch | query: string | Called when the search query changes. | | onSelect | option: SelectOption | Called when an option is selected. | | onDeselect | option: SelectOption | Called when an option is deselected (multiple mode). |
Slots
| Slot | Description |
|---|---|
| chip | Custom chip content in multiple mode ({ option }). |
| option | Custom option item content ({ option, selected }). |
| loading | Custom loading content in the dropdown (progress bar still renders above). |
| empty | Custom empty-state content. |
| beforeOptions | Content above the option list. |
| afterOptions | Content below the option list. |
Inherited from FormField
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Primary label text above the control. |
| description | string | — | Helper text below the control (hidden when invalid). |
| corner | string | — | Secondary label text at the inline end of the header row. |
| required | boolean | false | Shows a red asterisk on the label. |
| disabled | boolean | false | Whether the control is disabled. |
| readonly | boolean | false | Whether the control is read-only. |
| error | boolean | false | Applies invalid styling and hides description. |
| errorMessage | string | — | Error message below the control. |
| showErrorIcon | boolean | true | Shows an error icon when invalid. |
| hideErrorMessage | boolean | false | Does not reserve space for error messages. |
| variant | FormFieldVariant | "outline" | Visual variant of the field shell. |
| color | FormFieldColor | "primary" | Color applied to the field control. |
| size | FormFieldSize | "md" | Typography and control sizing. |
| rounded | FormFieldRounded | "md" | Border radius of the field control and the listbox dropdown panel. |
| start | string | — | Inline-start text inside the field (prefix). |
| end | string | — | Inline-end text inside the field (suffix). |
| startIcon | IconSource | — | Icon at the inline start. |
| endIcon | IconSource | — | Icon at the inline end. |
| errorIcon | IconSource | "alert" | Icon shown when invalid and showErrorIcon is enabled. |
| controlId | string | — | Associates labels and helper text with the control. Auto-generated when omitted. |
| classes | FormFieldClasses | — | Class overrides per part. |
| customProps | SelectCustomProps | — | FormField parts plus nested listbox, chip, and clearIcon. label accepts Label props (without children). |
| slots | FormFieldSlots | — | React slots. Vue: #label, #corner, default, #description, #errorMessage, #start, #end. |