Listbox

A painstakingly re-engineered select menu, just so you can put a flag in it or have a placeholder.

Active Paused Delayed Canceled
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Project status</Label>
	<Listbox name="status" defaultValue="active">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

Component API

PropDefaultDescription
Listbox extends the Headless UI <Listbox> component
disabledfalseWhether or not to disable the listbox.
invalidfalseWhether or not the listbox has a validation error.
name-The name to use when submitting an HTML form.
defaultValue-The initial value for the listbox.
value-The controlled value of the listbox.
onChange-Handler to call when the listbox value changes.
placeholder-The text to show when no option is selected.
ListboxOption extends the Headless UI <ListboxOption> component
value-The option value.
ListboxLabel extends the JSX <span> element
This component does not expose any component-specific props.
ListboxDescription 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 Listbox, ListboxOption, and ListboxLabel components to build a basic listbox:

Active Paused Delayed Canceled
<script>
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Listbox name="status" defaultValue="active">
	<ListboxOption value="active">
		<ListboxLabel>Active</ListboxLabel>
	</ListboxOption>
	<ListboxOption value="paused">
		<ListboxLabel>Paused</ListboxLabel>
	</ListboxOption>
	<ListboxOption value="delayed">
		<ListboxLabel>Delayed</ListboxLabel>
	</ListboxOption>
	<ListboxOption value="canceled">
		<ListboxLabel>Canceled</ListboxLabel>
	</ListboxOption>
</Listbox>

Make sure to provide an aria-label for assistive technology, or connect the Listbox to your own <label> element using an id.

With label

Wrap a Label and Listbox with the Field component to automatically associate them using a generated ID:

Active Paused Delayed Canceled
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Project status</Label>
	<Listbox name="status" defaultValue="active">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

With description

Use the Description component to add a description above or below your Listbox:

This will be visible to clients on the project.

Active Paused Delayed Canceled
<script>
	import { Description, Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Project status</Label>
	<Description>This will be visible to clients on the project.</Description>
	<Listbox name="status" defaultValue="active">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

With placeholder

Use the placeholder prop to add a placeholder to your Listbox when no value is selected:

Active Paused Delayed Canceled
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Project status</Label>
	<Listbox name="status" placeholder="Select status&hellip;">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

With avatars

Add an Avatar to a ListboxOption by inserting it before your ListboxLabel:

Assigned to

<script>
	import { Avatar } from '$lib/components/avatar';
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Assigned to</Label>
	<Listbox name="user">
		{#each users as user (user.id)}
			<ListboxOption value={user}>
				<Avatar
					src={user.avatarUrl}
					initials={user.initial}
					class="bg-purple-500 text-white"
					alt=""
				/>
				<ListboxLabel>{user.name}</ListboxLabel>
			</ListboxOption>
		{/each}
	</Listbox>
</Field>

With icons

Add an icon to a ListboxOption by inserting it before your ListboxLabel:

Left Right Justified
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
	import HeroiconsBars3BottomLeft16Solid from 'virtual:icons/heroicons/bars-3-16-solid';
	import HeroiconsBars3BottomRight16Solid from 'virtual:icons/heroicons/bars-3-16-solid';
	import HeroiconsBars316Solid from 'virtual:icons/heroicons/bars-3-16-solid';
</script>

<Field>
	<Label>Alignment</Label>
	<Listbox name="alignment" defaultValue="left">
		<ListboxOption value="left">
			<HeroiconsBars3BottomLeft16Solid />
			<ListboxLabel>Left</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="right">
			<HeroiconsBars3BottomRight16Solid />
			<ListboxLabel>Right</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="justified">
			<HeroiconsBars316Solid />
			<ListboxLabel>Justified</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

The ListboxOption component is designed to work best with 16×16 icons.

With flags

Add a flag icon to a ListboxOption by inserting it before your ListboxLabel, just like any other icon:

CanadaUnited StatesMexico
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
	import Flag from 'svelte-flagpack';
</script>

<Field>
	<Label>Assigned to</Label>
	<Listbox name="user" defaultValue={currentCountry}>
		{#each countries as country (country.code)}
			<ListboxOption value={country.code}>
				<Flag class="w-5 sm:w-4" code={country.code} />
				<ListboxLabel>{country.name}</ListboxLabel>
			</ListboxOption>
		{/each}
	</Listbox>
</Field>

We like the 16×12 flag icons from Flagpack, a great set of open-source flag icons.

With secondary text

Use the ListboxDescription component to add secondary text to a listbox option:

@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import {
		Listbox,
		ListboxDescription,
		ListboxLabel,
		ListboxOption
	} from '$lib/components/listbox';
</script>

<Field>
	<Label>User</Label>
	<Listbox name="user">
		{#each users as user (user.id)}
			<ListboxOption value={user}>
				<ListboxLabel>{user.name}</ListboxLabel>
				<ListboxDescription>@{user.handle}</ListboxDescription>
			</ListboxOption>
		{/each}
	</Listbox>
</Field>

Disabled state

Add the disabled prop to the Field component to disable a Listbox and the associated Label:

Active Paused Delayed Canceled
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field disabled>
	<Label>Project status</Label>
	<Listbox name="status" defaultValue="delayed">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

You can also disable a listbox outside of a Field by adding the disabled prop directly to the Listbox itself.

Validation errors

Add the invalid prop to the Listbox component to indicate a validation error, and render the error using the ErrorMessage component:

Active Paused Delayed Canceled

This field is required.

<script>
	import { ErrorMessage, Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field invalid>
	<Label>Project status</Label>
	<Listbox name="status" placeholder="Select a status&hellip;" invalid={errors.has('status')}>
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
	{#if errors.has('status')}
		<ErrorMessage>{errors.get('status')}</ErrorMessage>
	{/if}
</Field>

Constraining width

Use the class prop on the Listbox component to make layout adjustments like adjusting the max-width:

Monday Tuesday Wednesday Thursday Friday Saturday Sunday
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
</script>

<Field>
	<Label>Day of the week</Label>
	<Listbox class="max-w-40" name="day_of_the_week" defaultValue="Monday">
		<ListboxOption value="Monday">
			<ListboxLabel>Monday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Tuesday">
			<ListboxLabel>Tuesday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Wednesday">
			<ListboxLabel>Wednesday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Thursday">
			<ListboxLabel>Thursday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Friday">
			<ListboxLabel>Friday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Saturday">
			<ListboxLabel>Saturday</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="Sunday">
			<ListboxLabel>Sunday</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>

Be aware that the class prop is a sharp knife — make sure to only add classes that don't conflict with classes the component already includes or you'll get unexpected results.

With custom layout

Use the unstyled Field component from @headlessui/react directly instead of the styled Field component to implement a custom layout:

Active Paused Delayed Canceled
<script>
	import { Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';
	import * as Headless from '@headlessui/react';
</script>

<Headless.Field class="flex items-baseline justify-center gap-6">
	<Label>Project status</Label>
	<Listbox name="status" defaultValue="active" class="max-w-48">
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Headless.Field>

Using the unstyled Field component will ensure important accessibility features are still handled for you like generating IDs and associating elements using aria-* attributes.

Controlled component

Use the normal value and onChange props to use the Listbox component as a controlled component:

Active Paused Delayed Canceled
<script>
	import { Field, Label } from '$lib/components/fieldset';
	import { Listbox, ListboxLabel, ListboxOption } from '$lib/components/listbox';

	let status = $state('active');
</script>

<Field>
	<Label>Project status</Label>
	<Listbox name="status" value={status} onChange={(value) => (status = value)}>
		<ListboxOption value="active">
			<ListboxLabel>Active</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="paused">
			<ListboxLabel>Paused</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="delayed">
			<ListboxLabel>Delayed</ListboxLabel>
		</ListboxOption>
		<ListboxOption value="canceled">
			<ListboxLabel>Canceled</ListboxLabel>
		</ListboxOption>
	</Listbox>
</Field>