Listbox
- Source: https://github.com/reach/reach-ui/tree/main/packages/listbox
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#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] = React.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] = React.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
Prop | Type | Required | Default |
---|---|---|---|
arrow | boolean |node | false | "▼" |
button | node |func | false | |
children | node |func | true | |
defaultValue | string | false | |
disabled | boolean | false | false |
form | string | false | |
name | string | false | |
onChange | func | false | |
portal | boolean | false | true |
required | boolean | false | false |
value | string | false |
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
Prop | Type | Required | Default |
---|---|---|---|
children | node |func | true | |
defaultValue | string | false | |
disabled | boolean | false | false |
form | string | false | |
name | string | false | |
onChange | func | false | |
required | boolean | false | false |
value | string | false |
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
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] = React.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
Prop | Type | Required | Default |
---|---|---|---|
children | node |func | true |
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
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-selected] { /* styles for the option matching the current value of the input */}[data-reach-listbox-option][data-current-nav] { /* styles for the option matching the user's navigation selection */}[data-reach-listbox-option][aria-disabled="true"] { /* styles for disabled listbox options */}
ListboxOption Props
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
Prop | Type | Required | Default |
---|---|---|---|
label | string | false |
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>
ListboxGroupLabel 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.