Skip to content

Listbox

A listbox presents a list of selectable options in a popover, which is toggled on or off by a button. Visually and behaviorally it is similar to @reach/menu-button except that ListboxOption components hold a value. In this sense they are more directly comparable to HTML select elements.

(() => {  function BasicExample() {    let labelId = `taco-label--${useId()}`;    return (      <div>        <VisuallyHidden id={labelId}>Choose a taco</VisuallyHidden>        <Listbox aria-labelledby={labelId}>          <ListboxOption value="default">Choose a taco</ListboxOption>          <ListboxOption value="asada">Carne Asada</ListboxOption>          <ListboxOption value="pollo" label="Pollo" disabled>            Pollo <Tag>Sold Out!</Tag>          </ListboxOption>          <div style={{ background: "#ccc" }}>            <ListboxOption value="pastor" label="Pastor">              Pastor <Tag>Fan favorite!</Tag>            </ListboxOption>          </div>          <ListboxOption value="lengua">Lengua</ListboxOption>        </Listbox>      </div>    );  }  function Tag(props) {    return (      <span        style={{          display: "inline-block",          lineHeight: 1,          fontSize: 11,          textTransform: "uppercase",          fontWeight: "bolder",          marginLeft: 6,          padding: 4,          background: "crimson",          borderRadius: 2,          color: "#fff",        }}        {...props}      />    );  }  return <BasicExample />;})();

You can use Listbox as a simple standalone component, or compose its parts with ListboxInput.

(() => {  function ComposedExample() {    let labelId = `taco-label--${useId()}`;    let [value, setValue] = useState("pollo");    return (      <div>        <VisuallyHidden id={labelId}>Choose a taco</VisuallyHidden>        <ListboxInput          aria-labelledby={labelId}          value={value}          onChange={(value) => setValue(value)}        >          <ListboxButton arrow="" />          <ListboxPopover>            <ListboxList>              <ListboxOption value="default">Choose a taco</ListboxOption>              <ListboxOption value="asada">Carne Asada</ListboxOption>              <ListboxOption value="pollo">Pollo</ListboxOption>              <ListboxOption value="pastor">Pastor</ListboxOption>              <ListboxOption value="lengua">Lengua</ListboxOption>            </ListboxList>            <div              style={{                padding: "10px 10px 0",                marginTop: 10,                borderTop: "1px solid gray",              }}            >              <p>I really like tacos. I hope you enjoy them as well!</p>            </div>          </ListboxPopover>        </ListboxInput>      </div>    );  }  return <ComposedExample />;})();

Installation

From the command line in your project directory, run npm install @reach/listbox or yarn add @reach/listbox. Then import the components and styles that you need:

npm install @reach/listbox# oryarn add @reach/listbox
import {  Listbox,  ListboxInput,  ListboxButton,  ListboxPopover,  ListboxList,  ListboxOption,} from "@reach/listbox";import "@reach/listbox/styles.css";

Usage

// Basic listboxfunction Example() {  return (    <Listbox defaultValue="popeyes">      <ListboxOption value="bojangles">Bojangles'</ListboxOption>      <ListboxOption value="churchs">Church's</ListboxOption>      <ListboxOption value="kfc">KFC</ListboxOption>      <ListboxOption value="popeyes">Popeyes</ListboxOption>    </Listbox>  );}// Composed listbox componentsfunction ExampleComposed() {  return (    <ListboxInput defaultValue="popeyes">      <ListboxButton />      <ListboxPopover>        <ListboxList>          <ListboxOption value="bojangles">Bojangles'</ListboxOption>          <ListboxOption value="churchs">Church's</ListboxOption>          <ListboxOption value="kfc">KFC</ListboxOption>          <ListboxOption value="popeyes">Popeyes</ListboxOption>        </ListboxList>      </ListboxPopover>    </ListboxInput>  );}

Component API

Listbox

The wrapper component for the high-level listbox API.

<div>  <span id="my-label">Choose a topic</span>  <Listbox aria-labelledby="my-label" defaultValue="comedy">    <ListboxOption value="drama">Drama</ListboxOption>    <ListboxOption value="comedy">Comedy</ListboxOption>    <ListboxOption value="suspense">Suspense</ListboxOption>    <ListboxOption value="horror">Horror</ListboxOption>  </Listbox></div>

Controlled Listbox

If you want to control the state of the listbox's value, you can do so by passing value and onChange props. The value corresponds with the value of the selected ListboxOption.

const [value, setValue] = useState("comedy");return (  <div>    <span id="my-label">Choose a movie genre</span>    <Listbox aria-labelledby="my-label" value={value} onChange={setValue}>      <ListboxOption value="drama">Drama</ListboxOption>      <ListboxOption value="comedy">Comedy</ListboxOption>      <ListboxOption value="suspense">Suspense</ListboxOption>      <ListboxOption value="horror">Horror</ListboxOption>    </Listbox>  </div>);

Listbox Props

PropTypeRequired
arrowboolean \| nodefalse
buttonnode \| funcfalse
childrennode \| functrue
defaultValuestringfalse
disabledbooleanfalse
formstringfalse
namestringfalse
onChangefuncfalse
portalbooleanfalse
requiredbooleanfalse
valuestringfalse
Listbox arrow

arrow?: boolean | React.ReactNode

Renders a text string or React node to represent an arrow inside the ListboxButton.

<Listbox arrow /> // renders a default arrow character<Listbox arrow={<span></span>} /> // renders a component as an arrow character

If you want to customize the appearance and placement of the arrow inside the button further, you can drop down to the composed API and render the button's children.

Listbox button

button?: React.ReactNode | ((props: { value: string | null; label: string | null; }) => React.ReactNode)

A render function or React node to to render the Listbox button's inner content. See the API for the ListboxButton children prop for details.

Listbox children

children: React.ReactNode

Listbox should accept ListboxOption components as children.

You can also pass arbitrary elements as needed, but be careful when passing elements that have semantic value into listbox directly (such as button). If you need such elements in the popover, chances are they either need to be nested inside an option or elsewhere outside of the list of options. In the latter case, use ListboxInput and the composed API.

Listbox defaultValue

defaultValue?: string

The default value of an uncontrolled listbox.

Listbox disabled

disabled?: boolean

Whether or not the listbox is disabled.

Listbox form

form?: string

The ID of a form associated with the listbox and its hidden input field. If Listbox is passed directly inside of its associated form this prop can be omitted so long as the name prop is used.

Listbox name

name?: string

The name used for the listbo input's form value.

Listbox onChange

onChange?(newValue: string): void;

The callback that fires when the listbox value changes.

Listbox portal

portal?: boolean

Whether or not the popover should be rendered inside a portal. Defaults to true.

Listbox required

required?: boolean;

Whether or not the listbox input's form field is required.

Listbox value

value?: string

The current value of a controlled listbox.

ListboxInput

The top-level component and context provider for the listbox.

<div>  <span id="my-label">Choose a topic</span>  <ListboxInput aria-labelledby="my-label" defaultValue="comedy">    <ListboxButton />    <ListboxPopover>      <ListboxList>        <ListboxOption value="drama">Drama</ListboxOption>        <ListboxOption value="comedy">Comedy</ListboxOption>        <ListboxOption value="suspense">Suspense</ListboxOption>        <ListboxOption value="horror">Horror</ListboxOption>      </ListboxList>    </ListboxPopover>  </ListboxInput></div>

ListboxInput CSS Selectors

Please see the styling guide.

[data-reach-listbox-input] {}[data-reach-listbox-input][data-state="idle"] {}[data-reach-listbox-input][data-value="VALUE_REF"] {}

ListboxInput Props

PropTypeRequired
childrennode \| functrue
defaultValuestringfalse
disabledbooleanfalse
formstringfalse
namestringfalse
onChangefuncfalse
requiredbooleanfalse
valuestringfalse
ListboxInput children

children: React.ReactNode | ((props: { value: string | null; valueLabel: string | null; isExpanded: boolean; }) => React.ReactNode)

The composed listbox expects to receive ListboxButton and ListboxPopover as children. You can also pass in arbitrary wrapper elements if desired.

If you want access to the listbox's current value and associated label, or its expanded state, you can use a render function.

<ListboxInput>  {({ value, valueLabel, isExpanded }) => (    <ListboxButton>      <span data-value={value}>{valueLabel}</span>    </ListboxButton>    <ListboxPopover>      <ListboxList>        <ListboxOption value="apple">Apple 🍏</ListboxOption>        <ListboxOption value="orange">Orange 🍊</ListboxOption>        <ListboxOption value="banana">Banana 🍌</ListboxOption>      </ListboxList>    </ListboxPopover>  )}</ListboxInput>
ListboxInput defaultValue

defaultValue?: string

The default value of an uncontrolled listbox.

ListboxInput disabled

disabled?: boolean

Whether or not the listbox is disabled.

ListboxInput form

form?: string

The ID of a form associated with the listbox and its hidden input field. If Listbox is passed directly inside of its associated form this prop can be omitted so long as the name prop is used.

ListboxInput name

name?: string

The name used for the listbo input's form value.

ListboxInput onChange

onChange?(newValue: string): void;

The callback that fires when the listbox value changes.

ListboxInput required

required?: boolean;

Whether or not the listbox input's form field is required.

ListboxInput value

value?: string

The current value of a controlled listbox.

ListboxButton

The interactive toggle button that triggers the popover for the listbox.

ListboxButton CSS Selectors

Please see the styling guide.

[data-reach-listbox-button] {}[data-reach-listbox-button][aria-expanded="true"] {}[data-reach-listbox-button][aria-disabled="true"] {}

ListboxButton Props

PropTypeRequired
arrowboolean \| nodefalse
childrennode \| functrue
ListboxButton arrow

arrow?: boolean | React.ReactNode

Renders a text string or React node to represent an arrow inside the button`.

ListboxButton children

children: React.ReactNode | ((props: { value: string | null; label: string; isExpanded: boolean; }) => React.ReactNode)

A render function or React node to to render the button's inner content.

By default, the button will display the text label of the selected option as its inner content. This label can be pulled from the option's inner text content or explicitly provided to the ListboxOption component via the label prop. If you want to render the button differently from its default, you must pass children.

Rendering ListboxButton on the server

It's important to note that the ListboxButton's default inner content cannot be server-side rendered. On the initial render, the button has no contextual information about the available options in a Listbox. As each ListboxOption is rendered, it is registered in a context object and updated at the top of the Listbox tree, which evaluates the options and their props to determine which option is selectable and which label to display inside the button. If you need the inner content of the button on the first render you must control the listbox's state and keep its options' values and labels in data at the top of the tree, and render the button directly via children.

let options = { one: "One option", two: "Another option" };let [value, setValue] = useState(options.one);return (  <ListboxInput>    <ListboxButton>{options[value]}</ListboxButton>    <ListboxPopover>      <ListboxList>        {Object.keys(options).map((option) => (          <ListboxOption key={option} value={option} label={options[option]}>            {options[option]}          </ListboxOption>        ))}      </ListboxList>    </ListboxPopover>  </ListboxInput>);

ListboxArrow

A wrapper component for an arrow to display in the ListboxButton.

You can use your own wrapper component if you prefer, but if its inner content contains anything that can be read by assistive devices you should use aria-hidden on the wrapper element, as the arrow should have no semantic value.

ListboxArrow CSS Selectors

Please see the styling guide.

[data-reach-listbox-arrow] {}[data-reach-listbox-arrow][data-expanded] {}

ListboxArrow Props

PropTypeRequired
childrennode \| functrue
ListboxArrow children

children: React.ReactNode | ((props: { isExpanded: boolean }) => React.ReactNode)

Children to render as the listbox button's arrow. This can be a render function that accepts the listbox's isExpanded state as an argument.

ListboxPopover

The popover containing the list of options.

ListboxPopover CSS Selectors

Please see the styling guide.

[data-reach-listbox-popover] {}[data-reach-listbox-popover][hidden] {}

ListboxPopover Props

PropTypeRequired
childrennodetrue
portalbooleanfalse
positionfuncfalse
ListboxPopover children

children: React.ReactNode

ListboxPopover expects to receive ListboxList as its children.

ListboxPopover portal

portal?: boolean

Whether or not the popover should be rendered inside a portal. Defaults to true.

ListboxPopover position

position?(targetRect?: DOMRect | null; popoverRect?: DOMRect | null): React.CSSProperties

The positioning function for the popover.

ListboxList

The list containing all listbox options.

ListboxList CSS Selectors

Please see the styling guide.

[data-reach-listbox-list] {}

ListboxOption

A selectable option for the listbox.

ListboxOption CSS Selectors

Please see the styling guide.

[data-reach-listbox-option] {  /* styles for all listbox options */}[data-reach-listbox-option][data-current] {  /* styles for the option matching the current value of the input */}[data-reach-listbox-option][aria-selected="true"] {  /* styles for the option matching the user's navigation selection */}[data-reach-listbox-option][aria-disabled="true"] {  /* styles for disabled listbox options */}

ListboxOption Props

PropTypeRequired
disabledbooleanfalse
labelstringfalse
valuestringtrue
ListboxOption disabled

disabled?: boolean

Whether or not the option is disabled from selection and navigation.

ListboxOption label

label?: string

The option's human-readable label. This prop is optional but highly encouraged if your option has multiple text nodes that may or may not correlate with the intended value, or if the inner text is really long (all text will be read by a screen reader by default, which can create a confusing experience).

It is also useful if the inner text node begins with a character other than a readable letter (like an emoji or symbol) so that typeahead works as expected for the user.

ListboxOption value

value: string

The option's value. This will be passed into a hidden input field for use in forms when the option is selected.

ListboxGroup

A group of related listbox options.

ListboxGroup CSS Selectors

Please see the styling guide.

[data-reach-listbox-group] {}

ListboxGroup Props

PropTypeRequired
labelstringfalse
ListboxGroup label

label?: string

The text label to use for the listbox group. This can be omitted if a group contains a ListboxGroupLabel component. The label should always be human-readable.

ListboxGroupLabel

A label identifier for a ListboxGroup. This can be used in lieu of the label prop in ListboxGroup.

<ListboxGroup>  <ListboxGroupLabel>Veggies</ListboxGroupLabel>  <ListboxOption value="broccoli">Broccoli 🥦</ListboxOption>  <ListboxOption value="carrot">Carrot 🥕</ListboxOption>  <ListboxOption value="tomato">Tomato 🍅</ListboxOption></ListboxGroup>

ListboxGroup CSS Selectors

Please see the styling guide.

[data-reach-listbox-group-label] {}

useListboxContext

function useListboxContext(): { id: string | undefined; isExpanded: boolean; value: string | null; valueLabel: string | null }

A hook that exposes data for a given ListboxContext component to its descendants.