Material-UI docs contain a useful pre-built component called a “Transfer List”. You’ve likely used transfer lists before: swap selections from one side to the other and back again.
In this article I’ll take Material-UI’s enhanced transfer list and, well, enhance it even more.

I’ll style with elevation and box shadow, plus add in the sort and total swap functionality. I’ll also add a collapse action in the card header. The rest of the functionality is straight from the example in the docs.
As importantly, I’ll discuss and explain the underlying transfer list component from the docs.
For an introduction to the Material-UI Grid component, read here.
MUI Grid JSX
This is a trimmed down version of the JSX. Many styling props have been removed, but the structure is there:
<Grid>
<Grid item>
<Paper>{CardSelector("Choices", left, "left")}</Paper>
</Grid>
<Grid item>
<Grid container direction="column" alignItems="center">
<Button>
>
</Button>
<Button>
<
</Button>
<Button>
<SwapHorizIcon></SwapHorizIcon>
</Button>
</Grid>
</Grid>
<Grid item>
<Paper>{CardSelector("Chosen", right, "right")}</Paper>
</Grid>
</Grid>
The Transfer List is not really a unique component at all, but a combination of components. It’s structure is provided primarily by a <Grid>
that contains three columns. The outer columns are the <CardSelector>
components, the inner column is populated with buttons.
This structure was in the original Code Sandbox from the Material-UI docs. I added the <SwapHorizIcon>
button.
MUI CardSelector JSX
Another trimmed down JSX:
<Card style={{ display: "flex", flexDirection: "column" }}>
<CardHeader
avatar={
<Checkbox />
}
title={title}
subheader={`${numberOfChecked(items)}/${items.length} selected`}
action={
<IconButton
onClick={handleExpandClick}
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
}
/>
<Collapse in={expanded} className={classes.collapse}>
<List className={classes.list} dense component="div" role="list">
{items.map((value: number) => {
return (
<ListItem
onClick={handleToggle(value)}
>
<ListItemIcon>
<Checkbox />
</ListItemIcon>
<ListItemText />
</ListItem>
);
})}
<ListItem />
</List>
<Button
onClick={(e) => handleSortClick(e, items, side)}
>
Sort
</Button>
</Collapse>
</Card>
This is the JSX for CardSelector (what the original sandbox called ‘customList’). It is a <Card>
which wraps <CardHeader>
, <List>
, and <Button>
components.
I chose to add the sort button to the bottom of the CardSelector as a kind of footer. Another choice would have been to add it as the action
prop of the <CardHeader>
, and use an icon instead of text I probably would have used the <SwapVert>
icon.
I added the <Collapse>
component which wraps the list and sort button and paired it with action
in the <CardHeader>
. When the action
is triggered, the <Collapse>
is notified via the expanded
state var. An important item to notice is that each CardSelector gets its own expanded
state var to track state with:
const CardSelector = (
title: React.ReactNode,
items: number[],
side: string
) => {
const [expanded, setExpanded] = React.useState(true);
//...more JSX
The most interesting thing about the original CardSelector code is that the avatar prop of <CardHeader>
is actually the checkbox for selecting all. <CardHeader>
was a clever choice for organizing the select all functionality and text, and looping through a list of checkboxes is a natural choice for displaying the content.
Here’s how to style and use the MUI ListItemText component.
Sorting, Swapping, and Re-Rendering the Transfer List
Sorting and swapping update the left
and right
state variables in order to update the UI. For example, here’s the sort function:
const handleSortClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
items: number[],
side: string
) => {
const copyItems = [...items].sort((a, b) => a - b);
if (side === "right") {
setRight(copyItems);
} else {
setLeft(copyItems);
}
};
Notice how I had to use the spread operator to create a copy of the items
array. This copy was then passed to the setters for the state variables. If the state variables were internally sorted, React would not be aware of the sort and would not re-render the DOM. (Another way around this would be to use an observable.array with MobX).
Here’s the swap
function:
const handleSwap = () => {
setRight(left);
setLeft(right);
};
A useful thing about this simple function is that selected state is maintained in the swap.
More interestingly, let’s discuss the not
, intersection
, and union
functions in the original sandbox.
The not function:
function not(a: number[], b: number[]) {
return a.filter((value) => b.indexOf(value) === -1);
}
In plain English, this returns all values in array a
that are not in array b
. This is actually used to uncheck all items: setChecked(not(checked, items));
The intersection function:
function intersection(a: number[], b: number[]) {
return a.filter((value) => b.indexOf(value) !== -1);
}
This returns a new array with all values in both arrays. It’s used with the buttons that shift items from one list to the other.
The union function:
function union(a: number[], b: number[]) {
return [...a, ...not(b, a)];
}
This returns a new array that contains all items in array a
and all items in b
that are not also in a
. This is used in the Sandbox to keep any duplicates from occurring when the select all button is pressed.
Styling the Transfer List
I added elevation simply by wrapping the CardSelector
in <Paper elevation={3}>
. This gives a uniform box-shadow to the wrapped child.
I added a custom box-shadow to the <CardHeader>
: boxShadow: “0px 4px 80px grey”
. The 80px
sets the blur value on the shadow, making the edges less sharp. I also had to add marginTop: 4
to the List component immediately adjacent to the <CardHeader>
.
I liked the touches that these gave stylings gave to the transfer list. Additional custom styling could have been accomplished by targeting Card or CardHeader css classes. Take a look at some of those here.
This article about building a Material-UI Select component with Checkboxes will show you how to style Checkboxes, and here’s how to add them to the Treeview component.
Resources
Here’s more info on MUI Checkbox color and size.
Expand your JavaScript knowledge with these 50 difficult JavaScript questions!
Docs:
https://material-ui.com/components/transfer-list/#enhanced-transfer-list