Skip to content
EspañolEnglish
All components (204)

Transfer list

Choice · TransferList · .amset-transfer

What it is and when to use it

Two lists for distributing things: on the left what there is, on the right what is chosen, and in the middle the buttons that move what is marked or everything at once. For a user's permissions, a product's categories or a team's members.

When to use it

  • Distributing a catalog of up to a few dozen items, when it helps to see at once what goes in and what stays out.
  • search puts a field above each column, which filters only its own.
  • An item with disabled in items can neither be marked nor moved: for what is imposed from outside. "Add all" and "Remove all" respect it.
  • What is added enters in the order of the catalog, not in the order in which it was pressed.
  • It is controlled: selected says what is on the right and onChange receives the new keys after each move.

When not to

  • Choosing several without needing to see what is left out: MultiSelect, or SelectableList with selection="multiple".
  • The filters of a catalog: FilterGroup.

Keyboard: inside each list, the arrow keys move and Space or Enter mark and unmark. Tab passes from a list to the buttons in the middle and to the other list; a button with nothing to move is disabled and skipped.

Accessibility: the whole is a group named like the label, and each list is named with its title and its count ("Available: 7"). The arrow buttons have names: "Add to selected", "Add all to selected", "Remove from selected" and "Remove all from selected". Each move is announced ("2 added. 3 of 8 chosen."): without that, pressing an arrow makes no sound.

States and motion: the rows bring those of SelectableList (hover, pressed, checked with the tick that is drawn, focus inside, disabled) and the buttons, those of IconButton. What arrives in a column enters with a short fade and the count in the title jumps when it changes. When narrow the columns stack and the arrows turn sideways. disabled (data-disabled on the root) shows the distribution without allowing marking, searching or moving.

Sizes: sm, md (default) and lg apply to the rows (30, 36 or 44 px), the buttons, the search boxes and the titles, and change the minimum and maximum height of each list.

Fine-tuning: CSS variables on .amset-transfer: --amset-transfer-slot (between columns), --amset-transfer-title and --amset-transfer-count (their fonts), --amset-transfer-count-background and --amset-transfer-count-ink, --amset-transfer-height-min (both lists are at least this tall, so that the block does not jump around) and --amset-transfer-height-max (scrolling starts here; maxHeight changes it for both). The rows are adjusted with --amset-list-sel-*.

What it looks like

Available 2

Write
DeleteAdministrators only

Selected 1

Read

<TransferList label="Permissions" items={items} selected={elegidos} onChange={setElegidos} />

Props

Import it with import { TransferList } from '@amset/ui'. Required props are marked.

PropTypeWhat it does
itemsrequiredreadonly TransferItem[]—
labelrequiredstringRequired: it names the entire group ("User permissions").
onChangerequired(keys: readonly string[]) => void—
selectedrequiredreadonly string[]Controlled: the keys that are in the right column.
availableLabelstring—
classNamestringMerged with the library classes; it does not replace them.
disabledbooleanNothing is checked or moved: for viewing a distribution that cannot be changed.
maxHeightstringMaximum height of each list before scrolling. Without it, that of `--amset-transfer-height-max`.
renderItemOptionRender<TransferItem>Paints each element of the two columns: an avatar with the name, a permission with its description in another tone. What the screen reader reads is still `label`.
searchbooleanA search field above each column.
selectedLabelstring—
size"sm" | "md" | "lg"Rows, buttons, search boxes and titles at once: `sm`, `md` (the default) or `lg`. Rows are 30, 36 or 44 px, like the rest of the controls.

It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.

CSS variables

These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.

VariableDefault
--amset-transfer-slotvar(--amset-space-3)
--amset-transfer-titlevar(--amset-text-sm)
--amset-transfer-countvar(--amset-text-xs)
--amset-transfer-count-backgroundvar(--amset-divider)
--amset-transfer-count-inkvar(--amset-ink-2)
--amset-transfer-height-min11rem
--amset-transfer-height-max14rem

How to change it

.amset-transfer {
  --amset-transfer-slot: ...;
  --amset-transfer-title: ...;
}

In the test bench

Every state and variant, to try one at a time, on its page at ui.amset.tech: