Contained list
A row of a list, and the title bar that heads the list: a title, room for a marker before it and a control after it, at one of four heights. Use it for a flat list a reader scans; when the rows share columns worth comparing, use Data table.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
Components
<ContainedListHeader title="Components" />
<ContainedList
interactive
leading={<Tag>BT</Tag>}
title="Button"
trailing={<Button variant="ghost" size="icon-lg" aria-label="Button actions">…</Button>}
/>
<ContainedList
interactive
leading={<Tag>TB</Tag>}
title="Tabs"
trailing={<Button variant="ghost" size="icon-lg" aria-label="Tabs actions">…</Button>}
/>
<ContainedList
interactive
leading={<Tag>TG</Tag>}
title="Tag"
trailing={<Button variant="ghost" size="icon-lg" aria-label="Tag actions">…</Button>}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/contained-list.jsonimport { ContainedList } from '@/components/ui/contained-list'Anatomy
The header above, then rows: a 16-inset cell with the marker and the title, any further cells, and the trailing control flush to the right edge at the row’s height. A 1px rule sits under each row. Only the title is required, and it stays on one line and truncates.
Components
- LeadingOptionalAn icon, tag or other short marker.
- TitleRequiredTypography, set in Body/3.
- DescriptionOptionalThe code's own second line at Caption/1; the kit draws none. Extra large is the size laid out for it.
- CellsOptionalThe kit's Item 2 and Item 3, further text cells after the title, each inset 16.
- TrailingOptionalTag, Button, or a control, flush to the row's right edge. The kit draws a ghost icon Button at the row's height.
- HeaderOptionalThe kit's list title item, a separate export that sits directly above the rows. On page is a Title/5 heading on the page's background at the rows' height, with the rule under it; Disclosed is a 32px Caption/1 bar on elevation-01. It takes one action at its right edge, and the kit's Filterable search, an expandable Search sized to the bar (2.1.0, #267).
Variants
Size sets the row’s height: 32, 40, 48 or 64, with Extra large setting its content at the top, where the code’s description line fits. The list’s Type is its header: On page or Disclosed. Item 2 and 3 are further cells; Inset stops the divider 16 short of each end.
Components
Components
Components
States
Hover climbs one rung of the elevation ladder and active two, the same tone-step move as Button’s hover; the divider steps aside while they show. Focus is a ring inside the row, drawn when the caller’s link round it takes focus. Disabled dims the text and marker.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | 'sm' | 'md' | 'lg' | 'xl' | — | The kit's row Size, 32, 40, 48 and 64 tall. Large by default, as the kit's list uses it. Extra large sets the content at the top. |
| interactive | boolean | false | Visual only. Adds the pointer cursor and the hover and active tone steps. The row takes no role, no tab stop and no click handler, so the caller supplies the interactive element; a link wrapped round the row draws the focus ring inside it. |
| disabled | boolean | — | The kit's Disabled. Text and marker dim and the row stops responding; the caller disables its link or control too. |
| insetDivider | boolean | — | The kit's divider Inset. The rule under the row stops 16 short of each end. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
on-surfaceTitle, and the On page header.surfaceResting row background. The kit fills a raw white, un-migrated Carbon; surface reads the same in Light and works in Dark.elevationHover (elevation-02) and active (elevation-03), the kit's layer-hover-01 and layer-active-01, one and two rungs up the ladder. The Disclosed header sits on elevation-01.outlineThe row divider and the header rule, through the subtle step (outline-subtle, the kit's border-subtle-00). It steps aside on hover, active and focus.backgroundThe On page header, the kit's Background/background.primaryThe focus ring inside the row, through the family's focus step; and disabled text, through its disabled-content step.on-surface-variantDescription text, and the Disclosed header.textThe title at Body/3, the description at Caption/1, the On page header at Title/5 SemiBold.spacingRow heights and padding, the 16 inset and the gaps between slots.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Put a Tag, an icon or a short code in the leading slot. It is a marker, not a second title.
- Keep the trailing slot to one control cluster: a ghost icon Button at the row’s size, a Tag, or a Menu.
- Set interactive only when clicking the row really does something, and supply that something: wrap the row in a link, or put the Button in the trailing slot.
- Build list views and table rows from this row, so a hover change lands everywhere at once.
Don’t
- Stack two or three buttons in the trailing slot. When a row needs several actions, put a Menu there instead.
- Rely on the description to carry something essential. It is the code’s own, truncates to one line, and the kit does not draw it.
- Set interactive on a row that only displays information. The hover tells the reader it will respond.
- Give a list its own row highlight. Hover and active are the elevation ladder’s rungs, so every list moves together.
Accessibility
What the component does for you, and what it leaves to you.
- Roles
- The row is a plain container with no role. Wrap the rows in a list element, or a table, when the structure matters to the reader.
- Keyboard
- The row itself is never focusable, even when interactive: the prop is visual only. Keyboard access comes from the link that wraps the row or the control in its trailing slot.
- Focus
- A link wrapped round the row draws a 2px primary-focus ring inside it when it takes focus. The trailing control brings its own ring.
- Disabled
- disabled dims the row and stops its hover, and sets
aria-disabled; disable the link or control the row holds as well, since the row has none of its own. - Header
- The header renders a real heading, h3 unless you pass another level, so the list sits in the page’s outline.
- Contrast
- Title is on-surface and description on-surface-variant, one tone step lower. Both are measured against surface at the theme’s target.
- Truncation
- Title and description clip with an ellipsis. The full text is still in the DOM, so assistive tech reads all of it.
Figma parity
The kit's Contained list page ships 43 variants across 7 sets. One is public; the rest are the private row, cell and title items it is built from. The code is the row and the header, on every axis.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Type | On page · Disclosed | ContainedListHeader variant | The kit’s title item: Title/5 SemiBold on background at the rows’ height, or a 32px Caption/1 bar on elevation-01. One action at its right edge. |
| Search · Filterable search | Boolean | ContainedListHeader search | The governed Search, expandable, sized to the bar. Filtering the rows is the caller’s. |
| Size | Small · Medium · Large · Extra large | size | sm, md, lg and xl: 32, 40, 48 and 64. Extra large sets its content at the top. |
| State | Enabled · Hover · Focus · Active · Disabled | interactive · disabled | Hover and Active are pseudo-classes on an interactive row (governance rule 7): elevation-02 and -03. Focus is the ring a wrapping link draws. Disabled dims text and marker; the kit keeps the icon bright, a slip. |
| Item 2 · Item 3 | Booleans | cells | Further text cells, each inset 16. |
| Divider: Inset | True · False | insetDivider | The rule stops 16 short of each end. |
| Description | Not drawn | description | The code’s own, at Caption/1. Extra large is laid out for a second line it never draws. |
| Fill | Raw #ffffff | — | An un-migrated Carbon value. surface, which reads the same in Light. |
