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

PropDefaultDescription
Combobox extends the Headless UI <Combobox> component
disabledfalseWhether or not to disable the combobox.
invalidfalseWhether the combobox has a validation error.
anchorbottomWhere 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
disabledfalseWhether 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&hellip;"
	>
		{#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&hellip;"
	>
		{#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>