Skip to content

Transfer

Two lists and the arrows between them: everything that could be chosen on one side, everything that has been on the other. For a choice long enough that a field full of chips stops being readable.

tsx
import { Transfer } from 'neba';

<Transfer
  items={[
    { value: 'status', label: 'Status' },
    { value: 'commit', label: 'Commit' }
  ]}
  value={shown}
  onValueChange={setShown}
/>;

Props

PropTypeDefaultDescription
variantshared'solid' | 'outline' | 'text''outline'Weight of the surface each of the two panels draws
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and type scale
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only: never the height, never the type scale
items * readonly TransferItem[]Everything that can be on either side, in the order the lists show it
valuereadonly string[]What is on the right. With onValueChange it makes the pair controlled
defaultValuereadonly string[]What starts on the right, uncontrolled
onValueChange(value: string[]) => voidFired whenever something moves
sourceLabelReactNodeThe heading over the left-hand list. Defaults to the locale's word
targetLabelReactNodeAnd over the right-hand one
searchablebooleanfalsePuts a filter above each list. It hides rows; it never moves them
heightnumber | string220How tall each list is. Numbers are pixels
disabledbooleanfalseNothing can be ticked or moved
localestringBCP 47 tag the headings, buttons and filter are written in. Unsupported tags fall back to English

Every native <div> attribute passes through, apart from color.

TransferItem

PropTypeDefaultDescription
value * stringWhat identifies it, and what value is a list of
label * ReactNodeWhat the row says
disabledbooleanfalseIn the list but not movable

Below about a dozen options a Combobox with multiple, or a column of Checkboxes, is the smaller component. This one earns its size when the answer to "what did I actually pick" needs a list of its own.

Examples

value and onValueChange

The value is what is on the right, in the order items gives, so a row does not move when it is sent across and back. Ticking a row is not choosing it: ticks say which rows the next press moves, and the value says which side they are on.

searchable

Puts a filter above each list. It hides rows; it never moves them, and a hidden row is not part of a press.

height · disabled

height is how tall each list is. A disabled item stays in its list and can never be ticked or moved; disabled on the whole component does the same to all of them.

locale · sourceLabel · targetLabel

The headings, the buttons and the filter come from locale. sourceLabel and targetLabel write the two headings out instead, which is usually what you want: "Available" and "Selected" are rarely what the two lists actually are.

Accessibility

  • Every row is a real checkbox with its label wired to it, so the whole thing is reachable with Tab and Space.
  • The tick above each list is Select all for that list, and reports a mixed state while only some rows are ticked.
  • Both buttons are named ("Move to selected" and "Move to available"), and go unavailable while there is nothing for them to move.

Released under the MIT License