Transfer

Two lists and the buttons between them, for moving items from a source set into a selected one.

Usage

When to use two panels instead of one field, what the value is, and what Transfer leaves to you.

When to use

Transfer is two panels with buttons between them: the source set on the left, the chosen set on the right. Use it when picking is a task of its own, and the reader needs to see what they took and what is left at the same time.
  • Assigning from a fixed list to one thing: permissions to a role, modules to a plan, reviewers to a document.
  • Building something from a catalog: the columns a report shows, the widgets on a dashboard, the fields in an export.
  • Any choice the reader reviews before saving. The right panel is the review, which a dropdown cannot show.
Use something else when
  • The list is long and searchable. A Select with mode="multiple" does the same job in one line, with its own search and tags.
  • The set fits on screen (about ten items or fewer). A Checkbox group needs no counts, arrows or second panel.
  • The rows have columns the reader compares. Use a Table with rowSelection. For both, put a Table inside a panel with children, as below.
  • The order of the result matters. A move to the right puts the moved items at the top of the right panel, and there is no drag to reorder.

dataSource and targetKeys

Store what onChange hands you. Transfer moves nothing itself. It calls onChange with the new targetKeys and waits, so forgetting to store them is the most common bug: the arrows light up and nothing moves.
  • dataSource holds the items for both panels, as one flat list where every item has a key. Pass rowKey when your records use another field.
  • targetKeys is the right panel. It lists what sits there. Every other item in dataSource sits on the left.
  • selectedKeys is the checked rows across both panels, and it is optional: leave it out and Transfer tracks the selection itself. onSelectChange hands you the two halves.
  • onChange receives (targetKeys, direction, moveKeys). moveKeys is what moved, without disabled items, so read it for an undo or an audit line.
  • After a move, the selection on the side the items left is cleared. You do not need to reset it.

What a row shows

render draws a row from an item, the same way on both sides. It returns a node or an object.
  • A node for anything richer than text, like an avatar with a name and an email. Search then matches the text the row renders, which gets vague once the row is a layout.
  • An object, { label, value }, to say what search should match. label is typed ReactElement, so wrap a plain string.
  • Keep a row to one line where you can. A two-line row halves how much of the list shows while the reader compares the two sides.
  • A row only gets a hover tooltip when render returns text. Once it returns a layout, handle truncation yourself.
showSearch is off by default and turns on search in both panels. It also takes { placeholder, defaultValue } to change the default “Search here”.
  • Turn it on once a panel can hold more than a screenful. Below that, it costs two input rows for nothing.
  • Search on fields the row does not show. With showSearch, the default filter matches the text each row renders. Pass filterOption to match other fields, or return { label, value } from render and put the searchable text in value. filterOption wins when both are set.
  • filterOption receives (inputValue, item, direction), so the two sides can match differently.
  • onSearch tells you what was typed after filtering is done. Use it for analytics, or to fetch a wider dataSource.

Naming the sides and the buttons

titles names the two panels and actions the two move buttons. The English locale leaves both titles empty, so an unconfigured Transfer names neither side.
  • titles takes [source, target]. Name them for what they mean on this screen (“Available” and “Assigned”), not “Left” and “Right”, which flip in RTL.
  • actions takes [toRight, toLeft]. Two strings give you labeled buttons. operations is the deprecated name.
  • Your own action buttons can only add to disabled. A Button you pass in actions stays disabled while nothing is selected, even with disabled={false}. loading and your onClick still work, and your onClick runs before the move.
  • Passing actions replaces the kit's arrows, icons included.

One way

oneWay removes the left arrow and gives each row on the right its own remove button. Use it when moving back is an undo rather than a choice.
  • Rows come back one at a time. The header menu still offers “Remove all data”, and “Remove current page” with pagination.
  • Under oneWay, pass one action. oneWay has no left arrow, so a second element in actions renders a button that moves nothing.
  • The remove button's icon does not follow your icon pack.
  • The right panel loses its select-all checkbox, since there is nothing to select there. The left panel keeps its own.

Long lists and custom bodies

Two props change the panel body and leave the buttons, the counts and the moving to Transfer.
  • pagination pages both panels, ten rows at a time unless you pass { pageSize }. It also takes simple, showSizeChanger and showLessItems.
  • pagination removes the select-all checkbox and adds “Select current page” to the header menu, since “all” and “this page” are different choices.
  • Draw the panel body with children. It receives the panel's direction, filteredItems, selectedKeys and the select callbacks. Return undefined for one side to keep the default list there, like a Tree on the left and a list on the right.
  • For a Table, call onItemSelectAll(keys, "replace") and set showSelectAll={false} so the Table's header checkbox matches Transfer's selection. For a Tree, flatten the data for dataSource and set checkStrictly so a group does not move its children.
  • Some icons do not follow your icon pack. The search box's icons, the oneWay remove button and the page arrows of pagination stay Ant Design's. To replace the search box, set showSearch={false} and render your own Input in a children body, filtering the items yourself.

Disabled items

An item with disabled cannot be moved, selected by select-all or picked from a menu, and it never reaches onChange. It stays visible, so the reader still sees that it exists.
  • Disable the item rather than the whole control where you can. Transfer's own disabled turns off both panels, both buttons, both search boxes and the pager.
  • A grayed row does not say why. Say it in the row or in the panel footer.
  • Do not disable a row to mean “already assigned”. That is what the right panel is for.
  • status="error" colors the panels' borders for form validation. Show the message that explains it next to the field.