Display
Description List
A semantic key-value list for order details, profile echoes, and settings summaries, in a divided-rows or panel-grid layout.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/description-list.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "DescriptionList" component (no runtime
deps beyond the cn() class-merge helper).
Contract
- export interface DescriptionListItem { label: string; value: ReactNode;
span?: 1 | 2 } — span defaults to undefined (normal width); span: 2 makes
the item occupy the full row/cell, meant for long values like addresses.
- export interface DescriptionListProps extends
React.HTMLAttributes<HTMLDListElement> { items: DescriptionListItem[];
columns?: 1 | 2; variant?: "rows" | "grid" }.
- Defaults: columns = 2, variant = "rows". Merge className via cn() and
spread the remaining props on the root <dl>. No hooks, no browser APIs —
ships without "use client" and renders fine from a Server Component.
Behavior
- Render real semantic <dl>/<dt>/<dd> — never divs standing in for list
semantics. Each item's label goes in a <dt>, its value in a <dd>.
- "rows" variant: a single <dl> with divide-y divide-border between items.
Each item is its own grid row: grid-cols-[1fr_2fr] with the label on the
left and value on the right, items-baseline, py-3. When span is 2, that
item switches to grid-cols-1 instead — label stacks above value, both at
full row width, for content too long to fit the 2fr value column.
- "grid" variant: the whole <dl> is a CSS grid, sm:grid-cols-1 or
sm:grid-cols-2 depending on the columns prop (always collapses to a single
column below the sm breakpoint, regardless of columns). Each item is a
cell with the label above the value (mt-1 gap). When span is 2, the cell
gets sm:col-span-2 to stretch across both columns — only meaningful when
columns is 2.
- The columns prop only affects the "grid" variant; "rows" is always a
single-column stack of divided lines.
- Long values wrap (break-words on <dd>) instead of overflowing or getting
clipped.
Rendering & styling
- Semantic tokens only: text-sm text-muted-foreground on every <dt>, text-sm
text-foreground on every <dd>, divide-border for the rows variant's
dividers. No hex values, no palette classes — dark mode follows the host
theme for free.
- "rows": divide-y divide-border wrapper, each row grid grid-cols-[1fr_2fr]
gap-4 py-3 (or grid-cols-1 gap-1 when span is 2).
- "grid": grid grid-cols-1 gap-x-8 gap-y-6, plus sm:grid-cols-1 /
sm:grid-cols-2 from the columns prop.
Customization levers
- Layout mode: variant="rows" for a traditional divided detail page,
variant="grid" for a modern panel/card summary — swap freely, the item
data shape doesn't change.
- Density: columns (grid variant) controls how many label/value cells sit
per line at sm and above; it always collapses to one column on narrow
viewports.
- Long values: set span: 2 on any item whose value shouldn't be squeezed
into the normal column (addresses, bios, multi-line notes).
- Rich values: value is a ReactNode, so slot in a status dot, a copy button,
a small avatar + name row, or a formatted amount — the list itself stays
presentation-only and never assumes what a value looks like.
- Label width ratio: the rows variant's grid-cols-[1fr_2fr] split is a single
class to tune (e.g. [1fr_3fr] for a narrower label column).Concepts
- Semantic
<dl>— every render is a real definition list (<dl>/<dt>/<dd>), not divs pretending to be one; assistive tech announces label/value pairs correctly for free. - Rows vs grid layout — the same
itemsdata renders as divided label-left/value-right lines (rows, classic detail page) or as label-above/value-below panel cells (grid, modern summary); switchingvariantnever changes the data shape. - Full-row span override —
span: 2on an item breaks it out of the normal column split so a long value (an address, a bio) gets the whole width instead of being squeezed or wrapped awkwardly. - Responsive column collapse — the
gridvariant'scolumnsprop only governs the sm-and-above layout; every viewport narrower than that always renders a single column, so density never causes horizontal cramping. - Value as a slot, not a type —
valueis aReactNode, so a status dot, a copy button, or an avatar row can sit in a cell without the list component knowing anything about them.
Relative Time
Auto-refreshing "time ago" text — renders a real `<time>` element, formats with Intl.RelativeTimeFormat, and stays hydration-safe by reading the clock through useSyncExternalStore.
QR Code
A tokenized QR code renderer — encodes any string to SVG and inherits `currentColor`, so it auto-inverts in dark mode.