List
A display list of rows: a leading column, a tinted media well, a title over a subtitle, a value at the end and a description that wraps under all of them. Every row is one grid whose text line always lands immediately before the growing column, so the separator, the wrapped line and the title start on the same edge whatever slots a row carries. It owns no selection and no navigation — the stylesheet draws the looks, your markup keeps the state.
- Stable
- React 19
- Zero JS
- Named-line row grid
- 3 sizes
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Network Ethernet
Three rows on a card. The hairline between two of them starts where the text starts, not at the edge of the list.
Three exports, and only the first one is required reading: List is the <ul>,
ListItem is a row, ListSection is a heading between groups.
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Network Ethernet
Code
<List variant="inset" aria-label="Settings"><ListItem media={<WifiIcon />} title="Wi-Fi" subtitle="Casa" trailing="Connected" chevron /><ListItem color="info" media={<BellIcon />} title="Bluetooth" subtitle="3 devices" chevron /><ListItem title="Network" trailing="Ethernet" chevron /></List><ul class="wayra-list" aria-label="Usage list" data-variant="inset" data-size="md" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-body"> <span class="wayra-list-title">Network</span> </span> <span class="wayra-list-trailing">Ethernet<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul>title is the only required prop on a row. Every other slot is dropped from the
DOM when you leave it out — no empty wrappers, so :has() in the stylesheet can
key on what a row actually carries. Anything React renders as nothing drops a
slot: null, undefined, false and an empty string, so a conditional such as
trailing={unread > 0 && unread} never leaves an empty column behind. 0
renders, and stays. The same goes for children: when they render nothing, the
row falls back to its title.
Variants
Section titled “Variants”plain is a bare stack. inset puts the same rows on a flat card: a hairline
border on an opaque surface, the box corner, and overflow: clip so the first
and last row stay inside it. The border is what separates it from the page —
no shadow, no blur.
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Network Ethernet
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Network Ethernet
Code
<List variant="inset">…</List><List variant="plain">…</List><ul class="wayra-list" aria-label="Inset list" data-variant="inset" data-size="md" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-body"> <span class="wayra-list-title">Network</span> </span> <span class="wayra-list-trailing">Ethernet<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul><ul class="wayra-list" aria-label="Plain list" data-variant="plain" data-size="md" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-body"> <span class="wayra-list-title">Network</span> </span> <span class="wayra-list-trailing">Ethernet<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul>Three row heights — 40, 48 and 56px at the default unit — and everything inside
a row is a fraction of the one it stands on: the padding, the media well, the
column gap and the type size. Only --wayra-list-size changes between them.
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Wi-Fi Casa Connected
- Bluetooth 3 devices
- Wi-Fi Casa Connected
- Bluetooth 3 devices
Code
<List size="sm">…</List><List size="md">…</List><List size="lg">…</List><ul class="wayra-list" aria-label="Small rows" data-variant="inset" data-size="sm" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul><ul class="wayra-list" aria-label="Medium rows" data-variant="inset" data-size="md" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul><ul class="wayra-list" aria-label="Large rows" data-variant="inset" data-size="lg" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="info"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Bluetooth</span> <span class="wayra-list-subtitle">3 devices</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul>The slots in a row
Section titled “The slots in a row”- 1 Annual report Updated today 2.4 MB Closes the quarter with the figures from all three regions and a note appendix.
- 2 Meeting notes No attachments yet; the summary lands when the review finishes.
Six slots, in DOM order: leading, media, body, trailing, chevron, description.
Code
<ListItemleading="1"media={<FolderIcon />}title="Annual report"subtitle="Updated today"trailing="2.4 MB"description="Closes the quarter with the figures from all three regions…"chevron/><ul class="wayra-list" aria-label="Documents" data-variant="inset" data-size="md" style="inline-size: 20rem"> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-leading">1</span> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.75 4.25h4l1.5 2h7v7.5h-12.5z"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Annual report</span> <span class="wayra-list-subtitle">Updated today</span> </span> <span class="wayra-list-trailing">2.4 MB<span class="wayra-list-chevron" aria-hidden="true"></span></span> <span class="wayra-list-description">Closes the quarter with the figures from all three regions and a note appendix.</span> </li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-leading">2</span> <span class="wayra-list-body"> <span class="wayra-list-title">Meeting notes</span> </span> <span class="wayra-list-description">No attachments yet; the summary lands when the review finishes.</span> </li></ul>| Slot | What it is | Announced? |
|---|---|---|
leading |
A rank, an ordinal, a time. Centred, muted, tabular-nums. |
Yes — a rank is data. |
media |
The tinted well: an icon, an image, an Avatar. |
No, aria-hidden. |
title |
The row’s main line. Truncates with an ellipsis. | Yes. |
subtitle |
Second line, muted. Truncates as well. | Yes. |
trailing |
Value, badge or a real control at the end of the row. | Yes. |
chevron |
A disclosure mark, two borders on an empty box. | No, aria-hidden. |
description |
A full line under the row. The one part that wraps. | Yes. |
The well is aria-hidden, so the row’s meaning has to be in the title. An
icon-only row says nothing to a screen reader. mediaShape lands as
data-media-shape, the default rounded included; rounded is also what the
base rule already draws, so hand-written markup only needs the attribute for
circle.
children replaces the title-and-subtitle block with a body of your own; the
media and trailing slots stay where they are, and title is not rendered at all.
The wrapped line, and the grid under it
Section titled “The wrapped line, and the grid under it”This is the part that is not obvious from the props. Every row is a grid with
named lines, and the invariant is that the line called text sits immediately
before the growing column, whatever leads it:
/* A rank or a well: one column before the text. */.wayra-list-item { grid-template-columns: [start] auto [text] minmax(0, 1fr) [tail] auto [end];}
/* Neither: the text starts at the padding. */.wayra-list-item:not(:has(> :is(.wayra-list-leading, .wayra-list-media))) { grid-template-columns: [start text] minmax(0, 1fr) [tail] auto [end];}
/* Both: two columns before it. */.wayra-list-item:has(> .wayra-list-leading):has(> .wayra-list-media) { grid-template-columns: [start] auto auto [text] minmax(0, 1fr) [tail] auto [end];}:has() re-points the template; nothing else moves. The body takes
grid-column: text, and the description — the second row of the grid — takes
grid-column: text / end, so it starts under the title and runs to the far edge
however many columns came first. That is why the two descriptions in the example
above line up with their own titles rather than with each other.
The separator follows the same line. It is not a border on the list: it is the
::before of the row below, which is why the first row has none and why a
row can take its own inset away:
.wayra-list > li + li::before { inset-inline: var(--_wayra-list-rule-start) 0;}The variable defaults to padding + well + gap. A row with no well, and a row
with a leading column, both re-point it to the plain padding — a rank column is
not where the text of a row starts. One variable moves, never a property, and a
heading row cancels the rule under it outright, because a heading already
separates its group from the one above.
Sections
Section titled “Sections”ListSection is a heading row: small uppercase, muted, and an <li> like every
other child, so the list stays a valid list.
- Network
- Wi-Fi Casa Connected
- Ethernet
- Alerts
- Notifications 12
Code
<List variant="inset" aria-label="Settings"><ListSection>Network</ListSection><ListItem media={<WifiIcon />} title="Wi-Fi" subtitle="Casa" trailing="Connected" chevron /><ListItem media={<WifiIcon />} title="Ethernet" chevron /><ListSection>Alerts</ListSection><ListItem color="warning" media={<BellIcon />} mediaShape="circle" title="Notifications" trailing="12" chevron /></List><ul class="wayra-list" aria-label="Grouped settings" data-variant="inset" data-size="md" style="inline-size: 22rem"> <li class="wayra-list-section">Network</li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing">Connected<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 5.5a9 9 0 0 1 13 0M4 8.5a5.5 5.5 0 0 1 8 0"/><circle cx="8" cy="12" r="1" fill="currentColor" stroke="none"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Ethernet</span> </span> <span class="wayra-list-trailing"><span class="wayra-list-chevron" aria-hidden="true"></span></span> </li> <li class="wayra-list-section">Alerts</li> <li class="wayra-list-item" data-color="warning" data-media-shape="circle"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6.5a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 10.5z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Notifications</span> </span> <span class="wayra-list-trailing">12<span class="wayra-list-chevron" aria-hidden="true"></span></span> </li></ul>It is presentational: it names a group visually and adds nothing to the
accessibility tree. A group that has to be announced as a group is a nested
<ul> carrying its own aria-label, not this.
Selection is a look, not a widget
Section titled “Selection is a look, not a widget”The stylesheet ships the looks for selection and interactivity. It ships no behaviour at all:
| The library draws | You write |
|---|---|
[aria-selected="true"] — the accent fill, the inverted well, both hairlines taken out |
The attribute, and the state behind it |
data-interactive — a hover wash on rows, under @media (hover: hover); a selected row keeps its fill |
The click handler |
.wayra-list-item:focus-visible — an inward focus outline |
tabindex, and where it moves |
| Nothing | role="listbox" / role="option", and the keys |
- All files
- Recents
- Shared
Every ARIA attribute here is written by the consumer. The library contributed the fill.
Code
const [selected, setSelected] = useState(VIEWS[0]);
<List role="listbox" aria-label="Views" interactive>{VIEWS.map((view) => ( <ListItem key={view} role="option" aria-selected={view === selected} tabIndex={view === selected ? 0 : -1} media={<FolderIcon />} title={view} onClick={() => setSelected(view)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setSelected(view); } }} />))}</List><ul class="wayra-list" aria-label="Views" data-variant="inset" data-size="md" data-interactive="true" role="listbox" style="inline-size: 22rem"> <li class="wayra-list-item" data-color="primary" role="option" aria-selected="true" tabindex="0"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.75 4.25h4l1.5 2h7v7.5h-12.5z"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">All files</span> </span> </li> <li class="wayra-list-item" data-color="primary" role="option" aria-selected="false" tabindex="-1"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.75 4.25h4l1.5 2h7v7.5h-12.5z"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Recents</span> </span> </li> <li class="wayra-list-item" data-color="primary" role="option" aria-selected="false" tabindex="-1"> <span class="wayra-list-media" aria-hidden="true"><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.75 4.25h4l1.5 2h7v7.5h-12.5z"/></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Shared</span> </span> </li></ul>This is deliberate. A display list that grew a selection widget by accident would
ship half a listbox — the paint without the arrow keys, the type-ahead or the
single tab stop the ARIA pattern requires. interactive says what the rows
look like, not what they do.
Styling contract
Section titled “Styling contract”The class plus the data-* attributes are public API, and the stylesheet is
written against them — every preview on this page is hand-authored HTML with no
React in the page:
<ul class="wayra-list" aria-label="Settings" data-variant="inset" data-size="md"> <li class="wayra-list-section">Network</li> <li class="wayra-list-item" data-color="primary"> <span class="wayra-list-leading">1</span> <span class="wayra-list-media" aria-hidden="true"><svg …></svg></span> <span class="wayra-list-body"> <span class="wayra-list-title">Wi-Fi</span> <span class="wayra-list-subtitle">Casa</span> </span> <span class="wayra-list-trailing"> Connected<span class="wayra-list-chevron" aria-hidden="true"></span> </span> <span class="wayra-list-description">Home network, 5 GHz band</span> </li></ul>A hand-written <li> with none of the part classes still gets the separator and
position: relative: those rules are written against .wayra-list > li, not
against the row class. Five custom properties are yours:
| Property | Default | What it does |
|---|---|---|
--wayra-list-size |
calc(var(--wayra-size-field) * 12) at md |
The row-height step every other measurement is a fraction of. |
--wayra-list-pad |
calc(var(--wayra-list-size) * 0.32) |
Inline padding, and half of it block padding. |
--wayra-list-media |
calc(var(--wayra-list-size) * 0.6) |
The media well’s box. |
--wayra-list-gap |
calc(var(--wayra-list-size) * 0.24) |
Column gap between the slots. |
--wayra-list-radius |
var(--wayra-radius-box) |
The corner of an inset list. |
API reference
Section titled “API reference”ListProps extends ComponentProps<"ul">, so id, style, ref,
className, role, aria-label and every data-* land on the <ul>.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"plain" | "inset" |
"plain" |
inset puts the rows on a card. |
size |
"sm" | "md" | "lg" |
"md" |
Row-height step: 40 / 48 / 56px at the default unit. |
interactive |
boolean |
false |
Hover wash on rows. Emitted as data-interactive; wires no behaviour. |
children |
ReactNode |
— | The rows. Required. ListItem, ListSection or hand-written <li>s. |
unstyled |
boolean |
false |
Drops the wayra-list class, keeps every attribute. |
ListItem
Section titled “ListItem”ListItemProps extends ComponentProps<"li"> minus color and title — both
names are taken by slots below.
| Prop | Type | Default | Description |
|---|---|---|---|
color |
WayraColor |
"primary" |
Tints the media well and paints its glyph. Emitted as data-color. |
leading |
ReactNode |
— | Column before the well: a rank, an ordinal, a time. Stays announced. |
media |
ReactNode |
— | Icon or image for the well. Decoration — the well is aria-hidden. |
mediaShape |
"rounded" | "circle" |
"rounded" |
Emitted as data-media-shape, default included. |
chevron |
boolean |
false |
Appends a disclosure chevron at the end of the trailing slot. |
title |
ReactNode |
— | The row’s main line. Required. Truncates rather than wrapping. |
subtitle |
ReactNode |
— | Second line, muted. Single line as well. |
trailing |
ReactNode |
— | Value, badge or control at the end of the row. |
description |
ReactNode |
— | A full line under the row, aligned with the title. Wraps. |
children |
ReactNode |
— | Replaces the title/subtitle block. title is then not rendered. |
unstyled |
boolean |
false |
Drops wayra-list-item and every part class on this row. |
ListSection
Section titled “ListSection”ListSectionProps extends ComponentProps<"li">.
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
— | The heading text. Required. |
unstyled |
boolean |
false |
Drops the wayra-list-section class. |
unstyled is per instance: a row inside an unstyled list keeps its own class
unless it opts out too.
Accessibility
Section titled “Accessibility”- It is a real list. A
<ul>whose children are all<li>— rows and headings alike — so a screen reader announces “list, 5 items” and counts them. That is the whole reasonListSectionis an<li>and not an<h3>. - The meaning is in the title. The media well and the chevron are
aria-hidden; theleadingcolumn is not, because a rank is data. - Name the list. A bare
<ul>is announced as “list”. Passaria-label, or pointaria-labelledbyat a real heading above it — two lists on one page need telling apart. - Interactivity is yours, all of it. If a row can be clicked it needs a role,
a tab stop and keys. The APG checklist for a listbox is
role="listbox"on the list,role="option"plusaria-selectedon every row, one tab stop, and arrows that move it.interactivesupplies none of that — it is a wash. - Focus rings inward. A row has neighbours on both edges, so it takes an
outlineat a negative offset rather than the library’s shared halo, which would sit on the row above. The colour follows the row’s owndata-color. - Contrast. Titles are
base-contentonbase-100: 17.9:1 light, 17.7:1 dark. Subtitles, trailing values and descriptions are the shared muted colour: 5.64:1 light, 6.15:1 dark. Both clear AA for body text. The 75% fade on a selected row is unmeasured and unused — see the caution above. - Forced colors. An
insetlist takesCanvasTextfor its border, and a selected row takesHighlight/HighlightTextwithforced-color-adjust: none. The chevron is two borders on an empty box, so Windows high contrast keeps it — it needs no font to be present. - Right to left. Every measurement is logical, so a row mirrors on its own.
The chevron turns to point at the trailing edge under a
dir="rtl"on the row or any ancestor, which the browser floor understands;:dir()(Chrome 120) only puts an LTR island back. - Reduced motion. The one transition in the file — the row’s background —
goes to
0sthrough a variable. Nothing is disabled; nothing moves.
Do and don’t
Section titled “Do and don’t”Do
- All files
- Recents
A listbox that really is one: role="option" on every row, one tab stop, and arrow keys behind it.
Don't
- All files
- Recents
A listbox whose children are not options and which no key can reach. The fill is only paint — it promises a widget that is not there.
Do
- Network
- Wi-Fi Connected
ListSection renders an , so the list still owns nothing but list items.
Don't
- Wi-Fi Connected
Network
An straight inside the . Invalid HTML, an axe list violation, and the item count a screen reader reads out is now wrong.