A list box shows every option at once inside a bordered box, and can manage the
list as well as present it: rows can be added, deleted and dragged into a new
order without leaving the control. ListBoxField is the labeled form of it,
and the one place among the composed fields where the wrapper changes what the
control does rather than only what surrounds it.
Anatomy
Section titled “Anatomy”-
Container the bordered surface holding every item at once, scrolling pastPassportDriver’s licenceNational ID cardPassportDriver’s licenceNational ID cardmaxHeightrather than collapsing into a menu. -
List item one entry. It can carry its own actions menu and delete control.PassportDriver’s licenceNational ID cardPassportDriver’s licenceNational ID card -
Add button (optional) drawn fromPassportDriver’s licenceNational ID cardPassportDriver’s licenceNational ID cardonAdd, below the list rather than inside it. -
Delete button (optional) drawn fromPassportDriver’s licenceNational ID cardPassportDriver’s licenceNational ID cardonDelete, and disabled while noselectedKeysare passed at all — which is why the specimen has a selected row.
When to use
Section titled “When to use”Use a list box when the set of options is the information — an allowlist, a set
of accepted document types, the fields chosen for an export. Seeing all of them
together is the point, which is also the constraint: the box is 204px wide
unless you set fullWidth, and maxHeight caps its height and scrolls inside
the border. A list long enough to need that scroll has given up the one thing a
list box offers over a menu.
Use a Select when the list is only a way to reach one value, and a Combobox when it is long enough that the reader would rather type than look. Use Checkboxes for a handful of fixed options that nobody adds to — a list box spends a border, a footer and 204px on machinery those options never use.
ListBoxField moves Add and Delete out of the footer
Section titled “ListBoxField moves Add and Delete out of the footer”ListBoxField is a list box in a FormField, so it
brings the label, the helper text and the validation messages. It also takes
onAdd and onDelete away from the list box and rebuilds them beside the
label: Delete becomes the first item of an overflow menu, alongside anything
passed in actions, and Add becomes a plus icon button, or a dropdown of
options when given addDropdownConfig.
Those controls only render in config-field mode. configField is what draws
them, and it also sets fullWidth and drops the rows to sm. Outside it — a
ListBoxField standing alone, with no configField and no enclosing config
form group — onAdd, onDelete, actions, addDropdownConfig and
addDisabled are all accepted and none of them renders anything, because they
never reach the list box either. The footer buttons documented under
Editing belong to ListBox on its own.
So: a list box with a label inside a config form is a ListBoxField. A list box
that needs its footer is a ListBox, wrapped in a hand-composed
FormField if it needs a label with it.
ListBoxField also holds the selection itself, which ListBox does not: it
seeds state from defaultSelectedKeys, calls your onSelectionChange and then
stores the keys, so the menu can act on them. selectedKeys still overrides it.
Selection
Section titled “Selection”Set a selection mode before relying on the selection
Section titled “Set a selection mode before relying on the selection”Selection comes from React Aria’s GridList, which the props table records at
the bottom as the set of props this component also accepts. selectionMode is
one of them, and it governs whether rows can be selected at all: none,
single or multiple. Leave it out and rows are not selectable, which makes
Delete a button that receives nothing.
Selection is also controlled, not remembered. selectedKeys and
onSelectionChange are yours to hold, and onDelete is handed whatever
selectedKeys currently is — plural, because the API assumes more than one row
can be acted on at once.
Disable Delete yourself when nothing is selected
Section titled “Disable Delete yourself when nothing is selected”deleteDisabled is the only switch that does it. The component disables Delete
when selectedKeys is absent entirely, which covers the uncontrolled case and
nothing else: an empty Set is a present value, so a controlled list with no
rows selected renders Delete enabled and calls onDelete with an empty
selection. Derive deleteDisabled from the size of your own selection.
One more thing moves the selection without the reader doing it: while a row’s
dropdown is open, a mousedown anywhere outside the box clears the selection
through onSelectionChange. That is deliberate: it closes the row’s popover by
deselecting the row the popover belongs to. It does mean selection is not
something you can treat as sticky.
Editing
Section titled “Editing”Draw a footer button by supplying its handler
Section titled “Draw a footer button by supplying its handler”onAdd draws Add, onDelete draws Delete, and neither appears without its
handler. So a list with no handlers has no footer, and that is how a read-only
list is built.
editable gates the same pair, and both conditions have to hold. It defaults to
true, which makes it a veto rather than a switch: it can hide buttons that
exist, and it cannot summon buttons that do not. Passing editable on its own
shows nothing.
Omit the handlers for a genuinely read-only list
Section titled “Omit the handlers for a genuinely read-only list”editable={false} does not make a list read-only. With onAdd still supplied,
the empty state remains a button and still calls onAdd — the footer
disappears, the behavior does not. A per-row delete is a separate prop again:
onDelete on ListBox.Item draws an X on the row and editable does not reach
it.
Pass items as well as reorderConfig for drag and drop
Section titled “Pass items as well as reorderConfig for drag and drop”reorderConfig enables dragging. It takes onReorder, which receives the whole
reordered array to replace your state with, and itemKeyPath, the property name
that produced each row’s key. It needs the items prop too.
Supply reorderConfig while rendering rows as children without items and the
drag handles appear, the drag works, and the drop silently does nothing, because
the reorder handler returns early when it has no array to reorder.
Put a row’s own fields in its dropdown
Section titled “Put a row’s own fields in its dropdown”A row can be edited in place. dropdownFormFields or dropdownContent on
ListBox.Item open a popover when the row is selected, with
dropdownHeadingText and dropdownFooter framing it; actions gives the row
its own menu instead. What a list box cannot do is edit a row inline in the
list — the row is a label, and everything else happens in the surface above it.
Accessibility
Section titled “Accessibility”Expect grid roles, not list box roles
Section titled “Expect grid roles, not list box roles”The name is the widget, not the markup. This component is built on React Aria’s
GridList, so measured on this page’s own specimen the container is
role="grid", each row is role="row", and each row’s content sits in a
role="gridcell". There is no role="listbox" and no role="option" anywhere
in it.
That is the right choice for rows that carry their own buttons — a listbox
option cannot hold an interactive control, and these rows hold a delete icon and
an actions menu. It does mean any guidance written against the ARIA list box
pattern does not describe this component, and that aria-selected is set on
rows by React Aria rather than by you.
The grid takes a single tab stop and moves between rows with the arrow keys, so
a list of forty rows costs the keyboard one Tab. Give the container an
aria-label, since the border is the only thing naming it on screen.
A ListBoxField does not remove that obligation. Its label is visible text
above a role="grid" element, and a for attribute cannot name one, so the
grid is still unnamed until you pass aria-label as well.
Content
Section titled “Content”Write the empty text for the empty state you actually have
Section titled “Write the empty text for the empty state you actually have”emptyText fills two different blanks. With onAdd supplied the empty state is
a button, and emptyText is its label — write an action, and PDS falls back to
“Click to add an option”. Without onAdd it is a message, and PDS falls back to
“No options.” Those defaults are correct for their cases and a single hand-written
string will not be: “No document types yet” in a list you can add to leaves the
reader looking at a button that reads like a status.
Make rows distinguishable at a glance
Section titled “Make rows distinguishable at a glance”Rows are one line, clipped, with no secondary text, so two rows that read the same are two rows nobody can tell apart — including the reader who selects one to delete. Where the rows are user-generated, warn before accepting a duplicate rather than letting the list hold two.
Capitalization and the rules that apply to every label are on Writing.
ListBoxField first. Its table lists onAdd, onDelete, actions,
addDropdownConfig and addDisabled with nothing to say that all five render
only in config-field mode; the section above is that note. editable is in the
table too and is moot here, since the buttons it vetoes are never drawn.
| Name | Type |
|---|---|
actions |
|
addDropdownConfig |
|
addDisabled |
Disables the add button, e.g. while the list is read-only. |
label |
Renders |
inputId |
ID of the input element. Providing this will automatically link the label to the input. |
helperText |
Renders |
errorText |
Renders |
warningText |
Renders |
labelProps |
|
helperProps |
|
warningProps |
|
errorProps |
|
configField |
Whether to render the form field as a config field. This will render the helper text as a tooltip. |
configFieldInputWidth |
Override the width of the input area when |
rightActions |
Renders actions to the right of the label. |
onAdd |
Called when “Add” button is clicked. Only renders “Add” button if this prop is present. |
onDelete |
Called when “Delete” button is clicked. Only renders “Delete” button if this prop is present. |
deleteDisabled |
Whether or not delete button should be disabled. |
editable |
Whether or not the Add/Delete buttons should be shown. |
emptyText |
Text to display if no options are available. |
fullWidth |
Whether or not to expand the box to the full width of the container. |
reorderConfig |
When provided, enables drag and drop functionality. |
size |
Size of the list box. |
maxHeight |
Caps the list height and scrolls vertically inside the bordered container. |
Omit< ComponentPropsWithoutRef<typeof GridList<T>>, ‘slot’ | ‘autoFocus’ | ‘orientation’ | ‘shouldFocusWrap’ | ‘dragAndDropHooks’ | ‘layout’ >.List Box’s own props:
| Name | Type |
|---|---|
onAdd |
Called when “Add” button is clicked. Only renders “Add” button if this prop is present. |
onDelete |
Called when “Delete” button is clicked. Only renders “Delete” button if this prop is present. |
deleteDisabled |
Whether or not delete button should be disabled. |
editable |
Whether or not the Add/Delete buttons should be shown. |
emptyText |
Text to display if no options are available. |
fullWidth |
Whether or not to expand the box to the full width of the container. |
reorderConfig |
When provided, enables drag and drop functionality. |
size |
Size of the list box. |
maxHeight |
Caps the list height and scrolls vertically inside the bordered container. |
Omit< ComponentPropsWithoutRef<typeof GridList<T>>, ‘slot’ | ‘autoFocus’ | ‘orientation’ | ‘shouldFocusWrap’ | ‘dragAndDropHooks’ | ‘layout’ >.Was this page helpful?