List
A list of things to read and pick from: rows of leading media, text and trailing values, one action covering each item — and not a line of JavaScript, so it renders on a server.
Import
JSX
import List from '@box-kite/react/components/list';Usage
- ALAda Lovelace9:41The engine might compose elaborate and scientific pieces of music of any degree of complexity.
- GHGrace HopperYesterdayIt is easier to ask forgiveness than it is to get permission — so the compiler ships on Friday.
- ATAlan TuringMonWe can only see a short distance ahead, but we can see plenty there that needs to be done.
JSX
<List>
{people.map((person) => (
<List.Item key={person.id} chevron>
<List.Media variant="avatar">{person.initials}</List.Media>
<List.Content>
<List.Row>
<List.Title lines={1}>
<List.Action props={{ href: `#${person.id}` }}>{person.name}</List.Action>
</List.Title>
<List.Meta>{person.time}</List.Meta>
</List.Row>
<List.Description lines={2}>{person.text}</List.Description>
</List.Content>
</List.Item>
))}
</List>An item is a row, and its content is rows
A
List.Item is a flex row of three slots: List.Media leading, List.Content taking the room, List.End trailing. The content is a column, and each line of it is a List.Row — its parts side by side, the first taking the room and the last pushed to the end, so a title and its time, or a status and a count, line up down the list. Every part is optional, and an item with none of them is simply a flex row of whatever it holds.One line, two or three
A title alone is one line; a
List.Description under it is two; a List.Overline above it, or a description clamped at two lines, is three. That is Material's whole taxonomy, and it falls out of which parts are there.Text stops where you say
lines={1} ends a title or a description in an ellipsis, 2 and 3 clamp it. Unset it wraps, which is right for a name whose end matters as much as its start.A main row and a secondary one
secondary makes a row the quieter line under the main one — smaller and muted — which is the shape of a mail client, an order list or a pull-request queue: what it is and when on top, its state and its numbers below.- 2 min agoPaid · 3 items€42.00
- 1 h agoRefunded · 1 item€9.50
JSX
<List variant="card">
<List.Item>
<List.Content>
<List.Row>
<List.Title lines={1}>
<List.Action>Order 4182 · Grace Hopper</List.Action>
</List.Title>
<List.Meta>2 min ago</List.Meta>
</List.Row>
<List.Row secondary>
<Span>Paid · 3 items</Span>
<Span fontWeight={600} color="gray-900" theme={{ dark: { color: 'white' } }}>€42.00</Span>
</List.Row>
</List.Content>
</List.Item>
</List>A row that wraps
A row keeps to one line and squeezes its parts, which is what a title and a time want.
wrap lets the parts go onto a new line instead and starts them all at the start — a set of tags, a list of labels — and on List.Item it lets the slots themselves wrap, so a trailing control drops under the text on a narrow screen rather than crushing it.- Design reviewdesignresearchmobileaccessibilitytypographyq4roadmap
JSX
<List>
<List.Item wrap>
<List.Content minWidth={48}>
<List.Title>Design review</List.Title>
<List.Row wrap>
{['design', 'research', 'mobile', 'accessibility', 'typography', 'q4', 'roadmap'].map((tag) => (
<Span key={tag} px={2} borderRadius={999} fontSize={12} bgColor="gray-100" theme={{ dark: { bgColor: 'gray-800' } }}>
{tag}
</Span>
))}
</List.Row>
</List.Content>
<List.Actions>
<Button variant="secondary">Join</Button>
</List.Actions>
</List.Item>
</List>Leading and trailing
List.Media takes three shapes as variants — icon (a tinted square), avatar (a circle that clips) and image (a larger thumbnail) — or none, for a bare icon. List.End stacks a value and its label and aligns them to the end, in tabular figures, so the numbers in a column of items line up. A chevron on the item says that pressing it goes somewhere with more.- Home 5G
- Server · eu-westHealthy for 41 days99.98%Uptime
JSX
<List variant="card">
<List.Item chevron>
<List.Media variant="icon"><Icon size={5}><Wifi /></Icon></List.Media>
<List.Content>
<List.Title><List.Action>Wi-Fi</List.Action></List.Title>
</List.Content>
<List.End><List.Meta>Home 5G</List.Meta></List.End>
</List.Item>
<List.Item>
<List.Media variant="image" bgGradient={{ linear: 'br', colors: ['sky-400', 'indigo-500'] }} />
<List.Content>
<List.Overline>Server · eu-west</List.Overline>
<List.Title><List.Action>api-04</List.Action></List.Title>
<List.Description>Healthy for 41 days</List.Description>
</List.Content>
<List.End>
<Span fontWeight={600}>99.98%</Span>
<List.Meta>Uptime</List.Meta>
</List.End>
</List.Item>
</List>One action covers the item
List.Action goes in the title, and its ::after stretches over the whole item — so the whole row presses, the focus ring draws round the whole row, and the name a reader hears is the title alone rather than every value in it. What it is follows from what it is given: an href makes it a link, an htmlFor makes it the label of a checkbox (a press anywhere on the item ticks it), and neither makes it a button.A control of the item's own goes in List.Actions
A switch, a menu, a second button:
List.Actions lifts them above the covering action, so they take their own presses. Anything outside it — media, text, the end values — presses the action.A pressable item says so without a prop
The hover, pressed, current (
aria-current) and disabled states are read off the action with :has(), so an item with no action is plain text and one with an action looks pressable — nothing to keep in step.JSX
<List variant="card">
{people.map((person) => (
<List.Item key={person.id} selected={picked.includes(person.id)}>
<List.Actions>
<Checkbox id={`pick-${person.id}`} checked={picked.includes(person.id)} onChange={() => toggle(person.id)} />
</List.Actions>
<List.Content>
<List.Title><List.Action props={{ htmlFor: `pick-${person.id}` }}>{person.name}</List.Action></List.Title>
</List.Content>
</List.Item>
))}
<List.Item>
<List.Media variant="icon"><Icon size={5}><Bell /></Icon></List.Media>
<List.Content>
<List.Title><List.Action props={{ htmlFor: 'setting-notifications' }}>Notifications</List.Action></List.Title>
</List.Content>
<List.Actions>
<Switch id="setting-notifications" checked={notifications} onChange={toggleNotifications} />
</List.Actions>
</List.Item>
</List>Four looks, three sizes
Dividers are the default — the right look for a long list of things of one kind, where a frame round every item is noise.
variant="plain" drops them, "card" frames the whole list as one block (the settings screen), and "separated" frames each item on its own with a gap between. An item's size is sm (44px, still a finger's width), md (56px) or lg (72px).default
plain
card
separated
JSX
<List variant="separated">
<List.Item size="sm">…</List.Item>
</List>Labelled values
List.Fields is a <dl> of List.Fields, each a label over its value, as many to a line as fit at 8rem — two on a phone. Every value keeps its label for a screen reader as well as for the eye, which a row of bare values does not.- Order 4182€42.00Total
- Status
- Paid
- Items
- 3
- Placed
- 15 Oct
- Ships
- Tomorrow
JSX
<List variant="card">
<List.Item ai="start">
<List.Content>
<List.Overline>Order 4182</List.Overline>
<List.Row>
<List.Title lines={1}><List.Action>Grace Hopper</List.Action></List.Title>
<List.End><Span fontWeight={600}>€42.00</Span><List.Meta>Total</List.Meta></List.End>
</List.Row>
<List.Fields>
<List.Field label="Status">Paid</List.Field>
<List.Field label="Items">3</List.Field>
<List.Field label="Placed">15 Oct</List.Field>
<List.Field label="Ships">Tomorrow</List.Field>
</List.Fields>
</List.Content>
</List.Item>
</List>Groups, and a label that sticks
List.Group is an item holding its label and a list of its own, so a reader hears the label and then "list, 3 items". level makes the label a heading to jump between; sticky keeps it at the top of the screen while its items scroll under it, until the next group's label pushes it off — set vars={{ 'list-sticky-top': '4rem' }} on the list to clear an app's own header.Today
- ALThe engine might compose elaborate and scientific pieces of music of any degree of complexity.
- GHIt is easier to ask forgiveness than it is to get permission — so the compiler ships on Friday.
- ATWe can only see a short distance ahead, but we can see plenty there that needs to be done.
Yesterday
- ALThe engine might compose elaborate and scientific pieces of music of any degree of complexity.
- GHIt is easier to ask forgiveness than it is to get permission — so the compiler ships on Friday.
- ATWe can only see a short distance ahead, but we can see plenty there that needs to be done.
Earlier
- ALThe engine might compose elaborate and scientific pieces of music of any degree of complexity.
- GHIt is easier to ask forgiveness than it is to get permission — so the compiler ships on Friday.
- ATWe can only see a short distance ahead, but we can see plenty there that needs to be done.
JSX
<List variant="card">
<List.Group label="Today" level={3} sticky>
<List.Item>…</List.Item>
</List.Group>
<List.Group label="Earlier" level={3} sticky>
<List.Item>…</List.Item>
</List.Group>
</List>What a reader hears
A
<ul role="list"> — the role spelled out, because Safari stops reporting a list once its items lose their markers — and tag="ol" when the order means something. A list is not a composite widget: each action is a tab stop of its own, and the arrow keys stay the page's. When the arrows should move between options and pick one, the pattern is a listbox, which is Combobox or Select.Styling
Every part is a Box, so every prop is available on it and beats the part's own default. The style tree,
Box.components('list'), holds the paint and none of the spacing, because every engine entry carries it: list (variants plain, card, separated), list.item (the divider's colour, the pressed, hover and current states, and the selected variant), list.media (icon, avatar, image), list.muted (every quieter part), list.action (the focus ring) and list.label (a group's label). The hairlines and the tints are mixed from currentColor, so they follow any theme of yours with no rule for it. What the list's look says to its items travels as custom properties, because a list has no context to hand them down — and they are yours to set:JSX
<List
vars={{
'list-divider': 'slate-200', // the line between items
'list-divider-inset': '4.5rem', // start it at the text, not the edge
'list-item-radius': '0.5rem', // round each item's tint
'list-sticky-top': '4rem', // clear a fixed header
}}
>List props
Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on /box rather than repeated here.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | The items: List.Items, and List.Groups for sections. |
tag | 'ul' | 'ol' | — | ol when the order means something — steps, a ranking. Default ul. |
List.Item
One thing in the list: a flex row of its parts.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | The item's parts: List.Media, List.Content, List.End, List.Actions — or anything at all, in one row. |
size | ListItemSize | 'md' | The item's spacing. Default md, at least 56px tall; sm is 44px, still a finger's width; lg is 72px. |
wrap | boolean | false | Lets the item's parts wrap onto a second line when they do not fit, instead of squeezing. |
selected | boolean | false | Tints the item as chosen. The state a reader hears belongs on the control that chose it — a checkbox, aria-pressed. |
chevron | boolean | false | Ends the item with a chevron: pressing it goes somewhere with more. Decoration, so aria-hidden. |
List.Media
The leading slot: an icon, an avatar, a thumbnail —
variant is icon, avatar or image, or none for a bare icon.| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.Content
The middle of an item, taking the room: a column of rows and text, which may shrink past its content.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.Row
A row inside
List.Content: its parts side by side, the last at the end.| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What sits side by side: a title and its time, a status and a count. The first takes the room, the last sits at the end. |
wrap | boolean | false | Lets the row's parts wrap onto a new line — a set of tags — instead of pushing the last to the end. |
secondary | boolean | false | The quieter line under the main one: smaller and muted. |
List.Overline
A small line above the title: an id, a date, a category.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | The text. |
lines | ListLines | — | How many lines it runs to before it stops: 1 ends in an ellipsis, 2 and 3 are clamped. Unset, it wraps. |
List.Title
The item's name, and where its
List.Action goes.| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | The text. |
lines | ListLines | — | How many lines it runs to before it stops: 1 ends in an ellipsis, 2 and 3 are clamped. Unset, it wraps. |
List.Description
Muted supporting text under the title.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | The text. |
lines | ListLines | — | How many lines it runs to before it stops: 1 ends in an ellipsis, 2 and 3 are clamped. Unset, it wraps. |
List.Meta
A small muted value that never wraps — a time, a count — in tabular figures.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.End
The trailing slot: a value and its label, stacked and aligned to the end so a column of them lines up.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.Actions
Controls of the item's own — a switch, a menu, a checkbox — lifted above the action that covers the item.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.Action
The item's one primary action, stretched over the whole item.
| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What the action is called: the item's title, and nothing else, since it is the name a reader hears. |
props | ListActionTagProps | — | The element's attributes. An href makes it an <a>, an htmlFor a <label>; neither, a <button type="button">. |
tag | 'button' | 'a' | 'label' | — | The element, when what props holds does not already say: a with no href is a placeholder link. |
onClick | (event: React.MouseEvent<HTMLElement>) => void | — | Fires on a press anywhere on the item — and on Enter, and on Space for a button. |
List.Fields
A
<dl> of List.Fields, as many to a line as fit at 8rem each.| Prop | Type | Default | What it does |
|---|---|---|---|
children | React.ReactNode | — | What goes in it. |
List.Field
A labelled value: a
<dt> and a <dd> in a <div>, which is the one wrapper a <dl> allows.| Prop | Type | Default | What it does |
|---|---|---|---|
labelrequired | React.ReactNode | — | What the value is: the <dt>. |
children | React.ReactNode | — | The value: the <dd>. |
List.Group
A section of the list: its label, then its own
<ul> of items.| Prop | Type | Default | What it does |
|---|---|---|---|
labelrequired | React.ReactNode | — | What the items have in common: "Today", "A", "Unread". |
children | React.ReactNode | — | The group's items. |
level | ListGroupLevel | — | Makes the label a heading of this level, so a screen reader can jump between groups. Unset, it is plain text. |
sticky | boolean | false | Keeps the label at the top of the screen while its items scroll under it, until the next group's pushes it off. |
List keyboard
| Key | Result |
|---|---|
Tab | Each item's action, then each control in its List.Actions. |
Enter | Follows the action's link or presses its button; Space presses a button. |
List accessibility
- A
<ul role="list">: the role is spelled out because Safari drops a list's semantics once its items lose their markers.tag="ol"makes it an ordered one. List.Action's name is its own text, not the whole item's — a reader hears "Ada Lovelace, link", not every value in the row.- A list is not a composite widget: every action is its own tab stop and the arrow keys stay the page's. For arrow-key selection among options, that is a listbox —
ComboboxorSelect.
Swept with axe on every release, in this state:
List. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.List style tree
Every part the component draws is a node with a name, so a default can be restyled with
Box.components() instead of a selector — and a variant is a name too.listvariants: plain, card, separatedlist.itemvariants: selectedlist.mediavariants: icon, avatar, imagelist.mutedlist.actionlist.label