# ListItem

> Presentational row for menus, selects, palettes, and lists: leading icon, truncating label, end-aligned meta (text, Badge, Kbd, StatusDot, value), trailing icon, selection check, and a destructive tone. Consumers own semantics.

- Status: stable
- Group: display
- Page: https://www.digitaltableteur.com/design-system/components/list-item
- Contract: https://www.digitaltableteur.com/contracts/v1/ListItem.contract.json

## When to use

ListItem renders the visual anatomy of a menu, select, palette, or list row: a leading icon, a truncating label, end-aligned meta content, a trailing icon, and a selection check. It is purely presentational; the interactive wrapper (a Radix Item, a native li/option, or a button) owns role, focus, and events. meta stays exposed to assistive tech since it often carries information (a shortcut, a status, a value); icon, trailingIcon, and the selected check are decorative.

## When not to use

- Do not add role, tabIndex, or onClick to ListItem; the interactive wrapper (Radix Item, li, option, button) owns keyboard, focus, and events.
- Do not rely on selected/highlighted for semantics; they are visual only; pair them with real ARIA (aria-selected, aria-checked, data-highlighted) on the wrapper.
- Do not use disabled as the only signal for a non-interactive row; pair it with aria-disabled on the wrapper so assistive tech announces the state.

## Look-alikes

The contract names no look-alike with a deciding condition.

## Props

| Prop | Type | Default | Description | Required |
| --- | --- | --- | --- | --- |
| `children` | `React.ReactNode` | none | Primary label. Truncates with an ellipsis; never wraps. | Yes |
| `icon` | `React.ReactNode` | none | Leading icon node; rendered in a fixed gutter so labels column-align. | No |
| `meta` | `React.ReactNode` | none | End-aligned secondary content: muted small text, Badge, Kbd, StatusDot, or a value. Exposed to AT. | No |
| `trailingIcon` | `React.ReactNode` | none | Trailing icon after meta, always aria-hidden: use it for decorative glyphs such as a chevron. A glyph that carries meaning (an external-link arrow) is hidden from screen readers here, so say it in the label or meta too. | No |
| `selected` | `boolean` | `false` | Renders the check indicator in the trailing position. Visual only; semantic selection belongs to the consumer. | No |
| `tone` | `"neutral" \| "destructive"` | `neutral` | Destructive rows (deletions) use the error color treatment. | No |
| `disabled` | `boolean` | `false` | Visual disabled treatment via the canonical disabled tokens. The consumer carries aria-disabled. | No |
| `highlighted` | `boolean` | `false` | Parent-driven active row (combobox/palette). Radix menus work without it via [data-highlighted]. | No |
| `className` | `string` | none | No description in the contract. | No |

## Variants

- Available tones: `neutral`, `destructive` (default `neutral`).

## Invalid combinations

The contract records no invalid combinations.

## Keyboard

The contract records no keyboard interaction.

## Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

| Level | Rule | How it is checked |
| --- | --- | --- |
| MUST | ARIA: No own ARIA; the interactive wrapper (Radix Item, button, li, option) owns role, aria-selected/aria-checked, and aria-disabled. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Meta is exposed to assistive tech (no aria-hidden); icon, trailingIcon, and the selected check are decorative (aria-hidden). | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | Required stories produce no axe violations at the configured severity. | Automated by `npm run test:stories:matrix:ci`. WCAG 2.5.8. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by `npm run test:stories:matrix:ci`. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by `npm run test:stories:hc`. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-11-04. |
| SHOULD | Wrap ListItem in the element that owns interaction; Radix MenuItem/Item, a native li/option, or a button; and let that element carry role, tabIndex, and event handlers. | Not stated in the contract. |
| SHOULD | Use meta for content the user needs to hear or read (a Kbd shortcut, a StatusDot, a value); use trailingIcon only for purely decorative glyphs like a chevron. | Not stated in the contract. |
| SHOULD | Set tone="destructive" for irreversible actions (delete, remove) so the row picks up the error color treatment. | Not stated in the contract. |

## Install and import

- Install: `npm install @digitaltableteur/react` (version 0.9.1, restricted access on npm).
- Import: `import { ListItem } from "@digitaltableteur/react";`
- Entry point: `@digitaltableteur/react/content` exports it too.
- Styles: import `@digitaltableteur/react/style.css` once at the app boundary.
- Also available as the custom element `<dt-list-item>`; see the custom elements manifest.

## Links

- Machine-readable contract (Design System Contract 1.0): https://www.digitaltableteur.com/contracts/v1/ListItem.contract.json
- Markdown version of this page: https://www.digitaltableteur.com/design-system/components/list-item.md
- Component index as JSON: https://www.digitaltableteur.com/design-system/components.json
- All components: https://www.digitaltableteur.com/design-system/components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.
