Skip to content
Data Display

Transfer

A dual-list picker: move items between a source and a target list with checkboxes, per-panel search, and select-all. The classic pattern for assigning permissions, columns, tags, or members.

Check rows in either panel, then use the arrows to move them. The select-all checkbox in a panel header toggles everything currently visible in that panel.

Available6
Granted1

Import

import { Transfer } from "@mikenotthepope/substrateui"

With search

showSearch adds a filter to each panel. Select-all then applies to the filtered rows only, so you can bulk-move a subset.

Available6
Granted1

Controlled

Pass targetKeys with onChange to own the state — the standard shape for saving to a form or server.

Available6
Granted1

granted: [audit]

Labels

Both move buttons are icon-only, and the per-panel count is assembled from numbers. Override with the labels prop. This is one of the few label sets with no LabelsProvider key, so it is per instance only; wrap it in a component of your own if you use it in more than one place.

PropTypeDefaultDescription
searchPlaceholderstring"Search..."Placeholder for each panel's search input.
emptystring"No items"Shown when a panel has nothing in it.
moveToTargetstring"Move to target"Accessible name for the button that moves the selection across.
moveToSourcestring"Move to source"Accessible name for the button that moves it back.
itemCount(selected, total) => string"n/m"The per-panel count. A function so the separator and order are the caller's.

Accessibility

Each row is a <label> wrapping its checkbox, so the whole row is a click target and screen readers announce the item text with its checked state. Panel headers use a select-all checkbox labelled by the panel title, and report a mixed state when only some rows are checked. The move buttons carry explicit accessible names and disable themselves when nothing movable is selected. Items marked disabled are skipped by select-all and can never be moved.

API Reference

PropTypeDefaultDescription
dataSourceTransferItem[]Every item, across both lists. Each has key, label, and optional disabled.
targetKeysstring[]Keys in the target (right) list — controlled.
defaultTargetKeysstring[][]Keys in the target list initially — uncontrolled.
onChange(targetKeys, direction, movedKeys) => voidFired after a move with the next target keys and what moved.
selectedKeysstring[]Checked keys across both panels — controlled.
onSelectChange(selectedKeys: string[]) => voidFired when the checked set changes.
titles[ReactNode, ReactNode]["Source", "Target"]Panel headings.
showSearchbooleanfalseShow a search input in each panel.
listHeightnumber240Height of each scrollable list, in px.
disabledbooleanfalseDisable the whole control.
labelsTransferLabelsText overrides: searchPlaceholder, empty, moveToTarget, moveToSource, itemCount.

TransferItem

PropTypeDefaultDescription
keystringUnique key across the whole data source.
labelReact.ReactNodeRendered row content.
disabledbooleanDim the row and block selection — it can never be moved.