Listbox (Select)

Listboxes are a great foundation for building custom, accessible select menus for your app, complete with robust support for keyboard navigation.

Wade Cooper

Arlen Mccoy

Devon Webb

Tom Cook

Tayna Fox

Hellen Schmidt

Installation

To get started, install Headless UI via npm:

npm install @headlessui/react

Basic example

Listboxes are built using the `Listbox`, `Listbox.Button`, `Listbox.Options`, `Listbox.Option`and `Listbox.Label`components.

The `Listbox.Button`will automatically open/close the `Listbox.Options`when clicked, and when the menu is open, the list of items receives focus and is automatically navigable via the keyboard.

import { useState } from 'react'
import { Listbox } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds', unavailable: false },
{ id: 2, name: 'Kenton Towne', unavailable: false },
{ id: 3, name: 'Therese Wunsch', unavailable: false },
{ id: 4, name: 'Benedict Kessler', unavailable: true },
{ id: 5, name: 'Katelyn Rohan', unavailable: false },
]
function MyListbox() {
const [selectedPerson, setSelectedPerson] = useState(people[0])
return (
<Listbox value={selectedPerson} onChange={setSelectedPerson}>
<Listbox.Button>{selectedPerson.name}</Listbox.Button>
<Listbox.Options>
{people.map((person) => (
<Listbox.Option
key={person.id}
value={person}
disabled={person.unavailable}
>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
)
}

Selecting multiple values

To allow selecting multiple values in your listbox, use the `multiple` prop and pass an array to `value`instead of a single option.

import { useState } from 'react'
import { Listbox } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function MyListbox() {
const [selectedPeople, setSelectedPeople] = useState([people[0], people[1]])
return (
<Listbox value={selectedPeople} onChange={setSelectedPeople} multiple>
<Listbox.Button>
{selectedPeople.map((person) => person.name).join(', ')}
</Listbox.Button>
<Listbox.Options>
{people.map((person) => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
)
}

Using a custom label

By default the `Listbox` will use the button contents as the label for screenreaders. If you'd like more control over what is announced to assistive technologies, use the `Listbox.Label` component.

import { useState } from 'react'
import { Listbox } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function MyListbox() {
const [selectedPerson, setSelectedPerson] = useState(people[0])
return (
<Listbox value={selectedPerson} onChange={setSelectedPerson}>
<Listbox.Label>Assignee:</Listbox.Label>
<Listbox.Button>{selectedPerson.name}</Listbox.Button>
<Listbox.Options>
{people.map((person) => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
)
}

Using with HTML forms

If you add the `name` prop to your listbox, hidden `input` elements will be rendered and kept in sync with your selected value.

import { useState } from 'react'
import { Listbox } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function Example() {
const [selectedPerson, setSelectedPerson] = useState(people[0])
return (
<form action="/projects/1/assignee" method="post">
<Listbox
value={selectedPerson}
onChange={setSelectedPerson}
name="assignee"
>
<Listbox.Button>{selectedPerson.name}</Listbox.Button>
<Listbox.Options>
{people.map((person) => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
<button>Submit</button>
</form>
)
}

This lets you use a listbox inside a native HTML `<form>` and make traditional form submissions as if your listbox was a native HTML form control.

<input type="hidden" name="assignee[id]" value="1" />
<input type="hidden" name="assignee[name]" value="Durward Reynolds" />

Transitions

To animate the opening/closing of the listbox panel, use the provided `Transition` component. All you need to do is wrap the `Listbox.Options` in a`<Transition>`, and the transition will be applied automatically.

import { useState } from 'react'
import { Listbox, Transition } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function MyListbox() {
const [selectedPerson, setSelectedPerson] = useState(people[0])
return (
<Listbox value={selectedPerson} onChange={setSelectedPerson}>
<Listbox.Button>{selectedPerson.name}</Listbox.Button>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Listbox.Options>
{people.map((person) => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</Listbox>
)
}

By default our built-in `Transition` component automatically communicates with the `Listbox` components to handle the open/closed states. However, if you require more control over this behavior, you can explicitly control it:

import { useState } from 'react'
import { Listbox, Transition } from '@headlessui/react'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function MyListbox() {
const [selectedPerson, setSelectedPerson] = useState(people[0])
return (
<Listbox value={selectedPerson} onChange={setSelectedPerson}>
{({ open }) => (
<>
<Listbox.Button>{selectedPerson.name}</Listbox.Button>
{/*
Use the Transition + open render prop argument to add transitions.
*/}
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
{/*
Don't forget to add `static` to your Listbox.Options!
*/}
<Listbox.Options static>
{people.map((person) => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
)
}

Because they're renderless, Headless UI components also compose well with other animation libraries in the React ecosystem like Framer Motion and React Spring.

Accessibility notes

Focus management

When a Listbox is toggled open, the `Listbox.Options` receives focus. Focus is trapped within the list of items until Escape is pressed or the user clicks outside the options. Closing the Listbox returns focus to the `Listbox.Button` .

Mouse interaction

Clicking a `Listbox.Button` toggles the options list open and closed. Clicking anywhere outside of the options list will close the listbox.

Keyboard interaction

CommandDescription
EnterSpace

when `Menu.Button` is focused

Opens listbox and focuses the selected item

Esc

when listbox is open

Closes listbox

when menu is open

Focuses previous/next non-disabled item

HomeEnd

when listbox is open

Focuses first/last non-disabled item

EnterSpace

when listbox is open

Selects the current item

A-Za-z

when listbox is open

Focuses first item that matches keyboard input

Other

All relevant ARIA attributes are automatically managed.

Component API

Listbox

The main Listbox component.

Prop
Default
Description
`as`
`Fragment`
`String | Component`

The element or component the `Listbox` should render as.

`disabled`
`false`
`Boolean`

Use this to disable the entire Listbox component & related children.

`value`
`T`

The selected value.

`onChange`
`(value: T) => void`

The function to call when a new option is selected.

Render Prop
Description
`open`
`Boolean`

Whether or not the Listbox is open.

`disabled`
`Boolean`

Whether or not the Listbox is disabled.

Listbox.Button

The Listbox's button.

Prop
Default
Description
`as`
`button`
`String | Component`

The element or component the `Listbox.Button`should render as.

Render Prop
Description
`open`
`Boolean`

Whether or not the Listbox is open.

`disabled`
`Boolean`

Whether or not the Listbox is disabled.

Styled examples

If you're interested in predesigned component examples using Headless UI and Tailwind CSS, check out Tailwind UI — a collection of beautifully designed and expertly crafted components built by us.

It's a great way to support our work on open-source projects like this and makes it possible for us to improve them and keep them well-maintained.

A project by Luka Kosta