DataList
The read-only 'field: value' detail block — a real description list (<dl>/<dt>/<dd>) for demographics, order summaries, settings recaps, and entity metadata. Three responsive orientations, a token-driven label column, dividers, graceful empty values, truncation, and an opt-in copy affordance.
@garn/ui/data-listDefault
The default row layout — four field/value pairs.
- Name
- Ada Lovelace
- ada@analytical.dev
- Role
- Principal Engineer
- Joined
- March 12, 2024
Orientations
row · stacked · grid (aligned) side by side.
row (default)
- Order
- #10293
- Status
- Fulfilled
- Total
- $249.00
stacked
- Order
- #10293
- Status
- Fulfilled
- Total
- $249.00
grid (aligned)
- Order
- #10293
- Status
- Fulfilled
- Total
- $249.00
Dividers
Hairline rules between rows, with Badge / Avatar values.
- Plan
- Pro
- Seats
- 12 of 20
- Renews
- January 1, 2027
- Owner
- GHGrace Hopper
Copyable
Inline copy affordances on IDs / keys / emails.
- API key
- sk-live-9f2a7c1e8b40d35f6a9e0c4b1d7e2f8a
- Customer ID
- cus_Qh82Kd0fL
- billing@acme.co
Empty
Empty values — the default em-dash and a custom placeholder.
- Phone
- +1 (555) 010-2934
- Middle name
- Not set
- Backup email
- Not set
Sizes
sm · md · lg.
- Name
- Katherine Johnson
- Team
- Flight Dynamics
- Name
- Katherine Johnson
- Team
- Flight Dynamics
- Name
- Katherine Johnson
- Team
- Flight Dynamics
Overflow
Two long-value strategies — truncate (one line + title) vs wrap (default).
truncate (one line + title)
- User agent
- Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15
- Referrer
- https://example.com/very/long/path/that/keeps/going
wrap (default)
- User agent
- Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15
- Webhook
- whsec_4f9a2c7e8b40d35f6a9e0c4b1d7e2f8a3c5b
In Card
A patient detail panel inside a Card with a status Tag.
- MRN
- MRN-4827193
- Name
- Jordan Rivera
- DOB
- 1989-07-21 (35)
- Acuity
- Urgent
- Allergies
- Not set
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add data-list- Registry
data-list- Source
- data-list.tsx
- Deps
class-variance-authority@radix-ui/react-slotlucide-react- Registry deps
utilsuse-clipboarduse-announcebutton
Import
import { DataList } from "@garn/ui/data-list";Props
Generated from the component's TypeScript types.
orientation- Description
- No description.
- Type
"grid" | "row" | "stacked"- Default
row
size- Description
- No description.
- Type
"lg" | "md" | "sm"- Default
md
align- Description
- Cross-axis alignment of term↔value (`row`/`grid`). Default `baseline` so a Tag/Badge value optically sits on the term's text baseline.
- Type
"baseline" | "center" | "start"
density- Description
- Local density override; omit to inherit the ambient `[data-density]`.
- Type
"compact" | "spacious"
divider- Description
- Hairline rule between items (never above the first / below the last).
- Type
boolean
labelWidth- Description
- Width of the term ("label") column in `row`/`grid`. Pass a token-backed length (e.g. a CSS var or a Tailwind width via className) or a number of px; omit for the natural `max-content` width. Sets `--data-list-label-w`.
- Type
number | string
placeholder- Description
- Rendered for an empty value. Default an em-dash; `aria-hidden` with an sr-only "Not set" so the absence is meaningful to assistive tech.
- Type
React.ReactNode
truncate- Description
- Ambient truncation for every value (a value can still override).
- Type
boolean
Plus 280 inherited native <dl> attributes.
DataList.Item
align- Description
- Override the root's cross-axis alignment for this row.
- Type
"baseline" | "center" | "start"
Plus 280 inherited native HTML attributes.
DataList.Term
asChild- Description
- Render as the child element (e.g. a heading or link), merging props.
- Type
boolean
Plus 280 inherited native HTML attributes.
DataList.Value
asChild- Description
- Render as the child element (e.g. a link), merging props.
- Type
boolean
copyable- Description
- Show an inline copy affordance (a ghost Button) on hover / focus-within.
- Type
boolean
copyText- Description
- Text to copy when `copyable`; defaults to the value's rendered text.
- Type
string
placeholder- Description
- Override the root's empty placeholder for this value.
- Type
React.ReactNode
truncate- Description
- Truncate a long value to one line with an ellipsis + native title tooltip. Overrides the root's ambient `truncate`.
- Type
boolean
Plus 280 inherited native HTML attributes.
DataList.CopyButton
className- Description
- No description.
- Type
string
label- Description
- Accessible label. Default "Copy value".
- Type
string
valueRequired- Description
- The text written to the clipboard.
- Type
string
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="data-list"]| Property | Token | Tier |
|---|---|---|
rowGap | --garn-gap-stack | semantic · air |
columnGap | --garn-gap-stack | semantic · air |
Private instance vars (never externalize): --data-list-label-w
Overview
DataList presents a read-only set of term→value pairs about one entity — demographics, an order summary, a settings recap, a metadata sidebar. It renders real <dl>/<dt>/<dd> (DataList.Item / Term / Value), with empty values showing a placeholder + sr-only "Not set" and opt-in truncate/copyable. Use row for a classic detail block, grid when values must align across rows, stacked for narrow asides. For tabular records across many entities, use a Table.
Guidelines
Use it for one entity's facts, not a data grid. DataList describes a single thing (a user, an order); comparing many rows of records is a Table's job. Keep terms short and consistent, and order them by importance.
Show empty values honestly. Render a clear "not set" placeholder rather than collapsing the row, so the absence is itself information; the component announces it to screen readers. Make long values truncate with a tooltip rather than breaking the layout.
Pick the layout for the content. grid when value alignment aids scanning, row for prose-y detail, stacked for narrow columns. Use copyable on the values people actually copy (IDs, emails) and keep the markup semantic so AT reads it as a definition list.
Best practices
- Use the real parts (DataList.Item / .Term / .Value) so the <dl>/<dt>/<dd> semantics hold.
- Pick
grid(or setlabelWidth) when you want every value's left edge to line up. - Drop rich nodes (Tag, Badge, Avatar, a link) straight into DataList.Value — it composes with the badge family.
- Let an empty value render the placeholder (don't hide the row) so the field still reads as 'not set'.
- Use
copyablefor IDs / keys / emails the user will copy;truncatefor long single-line values.
- Don't fake a description list with styled <div>s — you lose the announced term↔value relationship.
- Don't use DataList for rows×columns of records — that's Table.
- Don't hand-place a copy button; let
copyablerender and wire the announced one. - Don't hardcode the label column width in px utilities — use
labelWidth(token-backed) which feeds the var.
Content guidelines
- Keep terms short and consistent (sentence case, parallel phrasing).
- Prefer a meaningful empty placeholder ('None on file', 'Not provided') over a bare dash where it adds clarity.
Accessibility
- Role
term- Focus
- Only the copy button and any asChild link/heading are focusable, each with a 2px --garn-ring focus ring.
- • Real <dl> / <dt> / <dd> semantics so the term↔value association is announced, not just visually implied — never fake it with styled <div>s.
- • A DataList.Item is a <div> wrapper (valid since HTML5.2); in `grid` it adopts the parent columns via subgrid so alignment never needs per-cell borders.
- • An empty value renders a tokenized, aria-hidden placeholder PLUS an sr-only 'Not set', so the absence is meaningful to assistive tech and the column still holds.
- • The copy affordance is a real focusable <button> (reachable by keyboard, visible on touch) that announces 'Copied' via a role=status polite live region.
- • Use asChild on Term/Value to host a heading or link without breaking the <dt>/<dd> semantics.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.