InputsReact
Two lists side by side: the items the user can pick from, and the items they picked.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<TransferList.Root>
<TransferList.Source>
<TransferList.Label />
<TransferList.List>
<TransferList.Item>
<TransferList.ItemIndicator />
<TransferList.ItemText />
</TransferList.Item>
</TransferList.List>
<TransferList.Empty />
</TransferList.Source>
<TransferList.Action />
<TransferList.Target />
</TransferList.Root>
// Not in this example: TransferList.FilterExamples
Filter
Office cities with a filter above each list; the move-all buttons take the shown cities only.
Reorder
Table columns whose shown order the user sets with Up and Down, Alt and the arrow keys, or drag and drop.
Controlled
Reviewer objects held by the app, one disabled, moved everyone at once through actionsRef and cleared from outside.
Form
Required pizza toppings in a Field, submitted in the chosen order and brought back by a reset.
API reference
41 props across 11 parts, in React syntax. className, style, render are listed once, under Shared props.
ActionTransferList.Action
A button that moves items: the selection to the other List, every shown item, or the chosen selection one place up or down. While its action does not apply it is aria-disabled and keeps focus. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
actionrequiredWhat the button does. | TransferListActionName | — |
focusableWhenDisabledWhether the button stays focusable while its action does not apply. | boolean | true |
EmptyTransferList.Empty
Content shown while its List shows no item, on the server too. It describes the empty List and has no ARIA role. Renders a <div>.
FilterTransferList.Filter
A search field that filters its List as the user types. Down moves focus to the first shown item; Escape clears the query. Renders an <input>.
| Prop | Type | Default |
|---|---|---|
valueThe query. | string | — |
defaultValueThe query when the component is initially rendered, and what a form reset brings back. | string | — |
onValueChangeEvent handler called when the query changes. | ((query: string, eventDetails: TransferListFilterChangeEventDetails) => void) | — |
ItemTransferList.Item
An option of a List: a Listbox item whose value is one of the Root's items. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
valuerequiredThe item this option shows: an entry of the Root's items (or of its value). | any | — |
indexThe zero-based position of the item among the List's shown items. | number | — |
ItemIndicatorTransferList.ItemIndicator
Indicates whether the item is selected. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
keepMountedWhether to keep the HTML element in the DOM when the item is not selected. | boolean | false |
ItemTextTransferList.ItemText
The text of an item. Renders a <div>.
LabelTransferList.Label
The visible name of a List. Its text also names the List in the announcements and in the Actions' default names. Renders a <div>.
ListTransferList.List
The listbox of one side: a multi-select Listbox of the side's items that the Filter keeps. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
selectionThe selected items of this List. | readonly Item[] | — |
defaultSelectionThe selected items when the component is initially rendered, and what a form reset brings | readonly Item[] | — |
onSelectionChangeEvent handler called when the selection of this List changes. | ((selection: Item[], eventDetails: ListboxRootChangeEventDetails) => void) | — |
orientationThe orientation of the List. | "horizontal" | "vertical" | 'vertical' |
loopFocusWhether to loop keyboard focus back to the first item when the end of the List is reached | boolean | true |
virtualizedWhether the items are rendered by an external virtualizer, so only some of the side's items | boolean | false |
highlightedValueThe value of the highlighted item (the one that holds focus). | Item | null | — |
onHighlightChangeEvent handler called when the highlighted item changes. | ((item: Item | null) => void) | — |
onItemHighlightedVirtualized only: called when keyboard navigation is about to move focus to the item at | ((itemValue: Item, eventDetails: ListboxRootHighlightEventDetails) => void) | — |
childrenThe items, or a function called for each item of the side that the Filter keeps, in order. | React.ReactNode | ((item: Item, index: number) => React.ReactNode) | — |
RootTransferList.Root
Groups the two lists of a transfer list and the actions that move items between them, and owns the value: the chosen items, in their order. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
itemsrequiredEvery item of the transfer list, on either side. | readonly Item[] | — |
valueThe chosen items, in their order. | readonly Item[] | — |
defaultValueThe chosen items when the component is initially rendered, and what a form reset brings | readonly Item[] | — |
onValueChangeEvent handler called when items are moved or the chosen items are reordered. | ((value: Item[], eventDetails: TransferListRootChangeEventDetails) => void) | — |
itemToStringLabelConverts an item to the text it is announced, filtered and matched by typeahead with. | ((item: Item) => string) | — |
itemToStringValueConverts an item to the string submitted with a form. | ((item: Item) => string) | — |
isItemEqualToValueCustom comparison logic used to determine if an item matches a value entry. | ((item: Item, value: Item) => boolean) | — |
isItemDisabledWhether an item is disabled: it cannot be selected, moved or dragged. | ((item: Item) => boolean) | — |
filterWhether an item matches a Filter's query. | ((item: Item, query: string) => boolean) | — |
reorderableWhether the chosen items can be reordered: the moveUp and moveDown Actions, Alt+Up and | boolean | false |
allowDragWhether items can be dragged between the two Lists, and within the chosen List when | boolean | false |
moveOnDoubleClickWhether a double click on an item moves it to the other List. | boolean | true |
disabledWhether the component should ignore user interaction. | boolean | false |
readOnlyWhether the user should be unable to select or move items. | boolean | false |
requiredWhether the user must choose at least one item before submitting a form. | boolean | false |
nameIdentifies the field when a form is submitted: one entry per chosen item, in order. | string | — |
formIdentifies the form that owns the hidden inputs. | string | — |
inputRefA ref to access the hidden input that carries required. | React.Ref<HTMLInputElement> | — |
labelsThe texts of the announcements and of the Actions' default names. | Partial<TransferListLabels> | — |
actionsRefA ref to imperative actions. | React.Ref<TransferListRootActions> | — |
SourceTransferList.Source
The available side: holds the Label, the Filter and the List of the items that are not chosen. Renders a <div>.
TargetTransferList.Target
The chosen side: holds the Label, the Filter and the List of the items in the value. Renders a <div>.
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| Space | Selects or clears the focused item | Item |
| ShiftArrowDown | Extends the selection from the last selected item | Item |
| ControlAMetaA | Selects every shown, enabled item of the list; again clears it | List |
| Enter | Moves the selected items, or the focused one, to the other list; never submits a form | Item |
| DeleteBackspace | In the chosen list, moves the selected items, or the focused one, back | Item |
| AltArrowUpAltArrowDown | With reorderable, moves the selected chosen items one place; focus stays on the item | Item |
| ArrowDown | In a filter, moves focus to the first shown item | Filter |
| Escape | In a filter with a query, clears it | Filter |
| EnterSpace | On a button, presses it; focus stays on the button | Action |
| Escape | During a drag, cancels it and says so | Item |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="group", named by the Field label, a Fieldset legend, or the app's aria-labelledby or aria-label; aria-disabled | the two lists and their buttons are said as one question |
| Root | a role="status" region as its last child, polite and atomic | each move is said once: "Sunroof moved to Chosen" |
| List | role="listbox" with aria-multiselectable, named by its Label through aria-labelledby; on the chosen list aria-required | each list is said with its name and its rule |
| List | tabindex="0" and aria-describedby its Empty while it shows no item | focus has a place after "move all", and the empty list says why |
| Item | role="option" with aria-selected on every option, aria-disabled from isItemDisabled | the selected and the not selected options are both said |
| Filter | type="search" with aria-controls its list and no name; the app gives its aria-label or a label | the filter is named and is never form data |
| Action | aria-disabled while it has nothing to move, never disabled; aria-keyshortcuts on the move buttons | focus stays on the button after a press, and its keys are declared |
Focus order
- Each list is one tab stop: its first selected item, else the item focused last, else the first enabled one; an empty list is a tab stop of its own.
- A key move keeps focus in the list, on the next item that stays, else the one before it, else the empty list.
- A button press keeps focus on the button. Nothing takes focus on mount or on a form reset; an invalid submit focuses the available list.