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…

import type { ReactNode } from "react"
import { cn } from "@/lib/utils"

export interface DescriptionListItem {
  label: string
  value: ReactNode
  /** Occupies the full row/cell width — use for long values like addresses. */
  span?: 1 | 2
}

export interface DescriptionListProps extends React.HTMLAttributes<HTMLDListElement> {
  items: DescriptionListItem[]
  /** Only affects the "grid" variant; collapses to 1 below sm regardless. */
  columns?: 1 | 2

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/description-list.json

Prompt

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 items data renders as divided label-left/value-right lines (rows, classic detail page) or as label-above/value-below panel cells (grid, modern summary); switching variant never changes the data shape.
  • Full-row span overridespan: 2 on 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 grid variant's columns prop 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 typevalue is a ReactNode, so a status dot, a copy button, or an avatar row can sit in a cell without the list component knowing anything about them.

On This Page