SvComboBox
An editable combobox: type to filter a portalled option list and pick a value.
Unlike an autocomplete, the value must come from the list - unmatched text reverts
on blur. Filtering is local by default, or hand it a loadOptions for remote search.
SvComboBox is the searchable single-select. It is controlled through value +
onChange, groups options by their group heading, and keeps the panel visible
outside any scroll container. Pass loadOptions and it turns into a debounced,
race-safe server search (local filtering switches off and the returned list is
shown as-is), with loading and empty-state text you can localize via messages.
Basic usage
<script lang="ts">
import { SvComboBox, type ListOption } from '@svgrid/grid'
const options: ListOption[] = [
{ value: 'us', label: 'United States' },
{ value: 'gb', label: 'United Kingdom' },
{ value: 'de', label: 'Germany' },
]
let value = $state<string | null>(null)
</script>
<SvComboBox label="Country" {options} {value} onChange={(v) => (value = v)} required />
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options |
ReadonlyArray<ListOption> |
[] |
The options. A group heading buckets them into sections. |
value |
string | number | null |
null |
The selected value. |
onChange |
(value: string | number | null) => void |
- | Fires with the newly picked value (or null when cleared). |
placeholder |
string |
'Select…' |
Input placeholder when empty. |
autoOpen |
boolean |
false |
Focus the input on mount. |
loadOptions |
(query: string) => Promise<ListOption[]> |
- | Remote, debounced search. Disables local filtering. |
minLength |
number |
1 |
Min chars before a remote search fires. |
debounce |
number |
250 |
Debounce (ms) before a remote search fires. |
messages |
Partial<ComboMessages> |
- | Override built-in strings (noResults / loading / typeMore). |
size |
sm | md | lg |
md |
Control height and font size. |
disabled |
boolean |
false |
Blocks interaction. |
label |
string |
- | Visible field label, wired to the control. |
hint |
string |
- | Helper text under the control. |
error |
string |
- | Error message; announced and styled when set. |
required |
boolean |
false |
Marks the field required. |
invalid |
boolean |
false |
Applies the invalid state. |
name |
string |
- | Emits a hidden input carrying the value for form posts. |
dir |
ltr | rtl | auto |
auto |
Text direction. |
ariaLabel |
string |
- | Accessible name when there is no visible label. |
id |
string |
- | Root id; label/hint/error ids derive from it. |
Options use the shared ListOption shape. ComboMessages
is { noResults: string; loading: string; typeMore: string }.
Patterns
Remote (server) search
Give loadOptions an async function and SvComboBox debounces the query, keeps
only the latest response, and shows a loading state - no local filtering:
<SvComboBox label="User" minLength={2}
loadOptions={async (q) => (await fetch(`/api/users?q=${q}`)).json()}
onChange={(v) => (userId = v)} />
Localized strings
Override the empty-state and loading text through messages:
<SvComboBox {options}
messages={{ noResults: 'Keine Treffer', loading: 'Laden…', typeMore: 'Tippen zum Suchen' }} />
Grouped options
Set a group on options and the filtered panel keeps its section headings.
Dependent remote search
Close loadOptions over another field so the query is scoped by an earlier pick -
here cities are searched within the chosen country:
<script lang="ts">
import { SvComboBox, type ListOption } from '@svgrid/grid'
let countryId = $state<string | null>(null)
let cityId = $state<string | number | null>(null)
const loadCities = async (q: string): Promise<ListOption[]> =>
(await fetch(`/api/cities?country=${countryId}&q=${q}`)).json()
</script>
<SvComboBox label="City" minLength={2} disabled={!countryId}
loadOptions={loadCities} value={cityId} onChange={(v) => (cityId = v)} />
Because loadOptions re-reads countryId on each call, switching the country
just scopes the next search - and the combobox keeps only the latest response, so
an in-flight request for the old country can never win.
Accessibility
- The input carries
role="combobox"witharia-expanded/aria-controls; the panel is arole="listbox"with a roving active option. - Type to filter,
ArrowUp/ArrowDownto move,Enterto pick,Escapeto dismiss; unmatched text reverts on blur so the value stays valid. label,hint, anderrorare wired viaaria-describedby; passariaLabelwhen there is no visible label.
See also
- Selection overview - the whole picker family at a glance.
- SvDropDownList - pick from a list without typing.
- SvAutoComplete - free text with suggestions (any value allowed).