Listbox
A painstakingly re-engineered select menu, just so you can put a flag in it or have a placeholder.
<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
| Prop | Default | Description |
|---|---|---|
Listbox extends the Headless UI <Listbox> component | ||
disabled | false | Whether or not to disable the listbox. |
invalid | false | Whether 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 | ||
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 Listbox, ListboxOption, and ListboxLabel components to build a basic listbox:
<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:
<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:
<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…">
<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:
<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:
<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:
<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:
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…" 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:
<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:
<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:
<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>