Combobox
For when you know exactly what you want, but are too lazy to scroll for it.
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> Component API
| Prop | Default | Description |
|---|---|---|
Combobox extends the Headless UI <Combobox> component | ||
disabled | false | Whether or not to disable the combobox. |
invalid | false | Whether the combobox has a validation error. |
anchor | bottom | Where to position the combobox dropdown. |
name | - | The name to use when submitting an HTML form. |
options | - | A collection of options to display in the combobox. |
displayValue | - | The string representation of your option. |
defaultValue | - | The initial value for the combobox. |
value | - | The controlled value of the combobox. |
onchange | - | Handler to call when the value changes. |
placeholder | - | The text to show when no option is selected. |
ComboboxOption extends the Headless UI <ComboboxOption> component | ||
value | - | The option value. |
ComboboxLabel extends the JSX <span> element | ||
| This component does not expose any component-specific props. | ||
ComboboxDescription extends the JSX <span> element | ||
| This component does not expose any component-specific props. | ||
Field extends the Headless UI <Field> component | ||
disabled | false | Whether or not to disable the entire field. |
Label extends the Headless UI <Label> component | ||
| This component does not expose any component-specific props. | ||
Description extends the Headless UI <Description> component | ||
| This component does not expose any component-specific props. | ||
ErrorMessage extends the Headless UI <Description> component | ||
| This component does not expose any component-specific props. | ||
Examples
Basic example
Use the Combobox, ComboboxOption, and ComboboxLabel components to build a basic combobox:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
</script>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
aria-label="Assigned to"
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox> Make sure to provide an aria-label for assistive technology, or connect the Combobox to your own <label> element using an id.
With label
Wrap a Label and Combobox with the Field component to automatically associate them using a generated ID:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With description
Use the Description component to add a description above or below your Combobox:
This user will have full access to the project.
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Description, Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Description>This user will have full access to the project.</Description>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With placeholder
Use the placeholder prop to add a placeholder to your Combobox when no value is selected:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
placeholder="Select user…"
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With avatars
Add an Avatar to a ComboboxOption by inserting it before your ComboboxLabel:
<script>
import { Avatar } from '$lib/components/avatar';
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<Avatar
src={user.avatarUrl}
initials={user.initials}
class="bg-purple-500 text-white"
alt=""
/>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With flags
Add a flag icon to a ComboboxOption by inserting it before your ComboboxLabel:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
import Flag from 'svelte-flagpack';
</script>
<Field>
<Label>Country</Label>
<Combobox
name="country"
options={countries}
displayValue={(country) => country?.name}
defaultValue={currentCountry}
>
{#snippet children(country)}
<ComboboxOption value={country}>
{@const Flag = country.flag}
<Flag class="w-5 sm:w-4" />
<ComboboxLabel>{country.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> We like the 16×12 flag icons from Flagpack, a great set of open-source flag icons.
With secondary text
Use the ComboboxDescription component to add secondary text to a combobox option:
<script>
import {
Combobox,
ComboboxDescription,
ComboboxLabel,
ComboboxOption
} from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
<ComboboxDescription>{user.role}</ComboboxDescription>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> Disabled state
Add the disabled prop to the Field component to disable a Combobox and the associated Label:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field disabled>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> You can also disable a combobox outside of a Field by adding the disabled prop directly to the Combobox itself.
Validation errors
Add the invalid prop to the Combobox component to indicate a validation error, and render the error using the ErrorMessage component:
A user is required.
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { ErrorMessage, Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
invalid
name="user"
options={users}
displayValue={(user) => user?.name}
placeholder="Select user…"
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
<ErrorMessage>A user is required.</ErrorMessage>
</Field> Constraining width
Use the className prop on the Combobox component to make layout adjustments like adjusting the max-width:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Field, Label } from '$lib/components/fieldset';
</script>
<Field>
<Label>Currency</Label>
<Combobox
class="max-w-40"
name="currency"
options={currencies}
displayValue={(currency) => currency?.code}
defaultValue={currentCurrency}
>
{#snippet children(currency)}
<ComboboxOption value={currency}>
<ComboboxLabel>{currency.code}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With custom layout
Use the unstyled Field component from @headlessui/react directly instead of the styled Field component to implement a custom layout:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox';
import { Label } from '$lib/components/fieldset';
import * as Headless from '@headlessui/react';
</script>
<Headless.Field class="flex grow items-baseline justify-center gap-6">
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
class="max-w-48"
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Headless.Field> Controlled component
Use the normal value and onchange props to use the Combobox component as a controlled component:
<script>
import { Combobox, ComboboxLabel, ComboboxOption } from '$lib/components/combobox'
import { Field, Label } from '$lib/components/fieldset'
let user $state(currentUser);
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
value={user}
onchange={(value) => user = value}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
</ComboboxOption>
{/snippet}
</Combobox>
</Field> With custom filtering
Use the filter prop to customize how the Combobox filters its options:
<script>
import { Combobox, ComboboxDescription, ComboboxLabel, ComboboxOption } from '$lib/components/combobox'
import { Field, Label } from '$lib/components/fieldset'
</script>
<Field>
<Label>Assigned to</Label>
<Combobox
name="user"
options={users}
displayValue={(user) => user?.name}
defaultValue={currentUser}
filter={(user, query) =>
user.name.toLowerCase().includes(query.toLowerCase()) ||
`@${user.handle}`.toLowerCase().includes(query.toLowerCase())
}
>
{#snippet children(user)}
<ComboboxOption value={user}>
<ComboboxLabel>{user.name}</ComboboxLabel>
<ComboboxDescription>@{user.handle}</ComboboxDescription>
</ComboboxOption>
{/snippet}
</Combobox>
</Field>