Property List
Record-facts rows — an icon + label column beside a value column, rendered as an accessible definition list.
- Status
- Since
0.3.0- Accessibility pattern
- native description list
Last updated
- Domains
- Name
- Team
- Categories
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Property List before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify property-listInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/property-listRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
Usage
import {
PropertyLabel,
PropertyList,
PropertyRow,
PropertyValue,
} from "@/components/ui/property-list";
<PropertyList aria-label="Record details">
<PropertyRow>
<PropertyLabel icon={<Globe />}>Domains</PropertyLabel>
<PropertyValue>
<a href="…">attio.com</a>
</PropertyValue>
</PropertyRow>
<PropertyRow>
<PropertyLabel icon={<Users />}>Team</PropertyLabel>
<PropertyValue>
<span className="text-sm text-muted-foreground">Set a value…</span>
</PropertyValue>
</PropertyRow>
</PropertyList>;Anatomy
A <dl> of <dt>(label)/<dd>(value) pairs. The list is a named container
(@container/property-list), and the row layout follows the PANE's width rather than the viewport's
— the same facts pane is as often a narrow sidebar on a wide screen as a wide column on a narrow one:
- At
@xsand up — two tracks. The label track shrinks to its content above an 80px floor (minmax(calc(var(--spacing) * 20), max-content)), so short labels stop wasting the value column's width and long ones are no longer clipped by a track that never negotiated with them. - Below
@xs— the row stacks, and the value gets the full width instead of a sliver.
Values wrap; they do not truncate. A value is the point of its row, and the overflow: hidden
that truncation implies also clipped the focus ring of any link inside it. Compose
TruncatedText explicitly where a single line is genuinely
required. Values are compositions: plain text, a link, a TagGroup, or a muted placeholder span for the
"Set a value…" slot.
Semantics
The definition list announces each label→value relationship natively. Label icons are
decorative (aria-hidden); keep the text label present — never an icon-only <dt>.
PropertyList vs DataList vs SettingsRow
PropertyList | DataList | SettingsRow | |
|---|---|---|---|
| Shape | key→value pane | homogeneous <table> | label + control row |
| For | record facts, metadata | collections of records | preference forms |
Anatomy
Property List is a compound component. Every exported part, with the
data-slot it renders (generated from the canonical source):
Examples
Compose plain values, links, empty values, and tag groups in the same aligned definition list. Long values wrap onto as many lines as they need; the label track sizes itself to the longest label above its 80px floor. Rows are top-aligned: the label sits on the value's first line, so a value that wraps keeps its label beside its opening line, and a quiet picker or select trigger shares the label's centre like plain text does.
- Domains
- Name
- Team
- Categories
Narrow panes
In a narrow record pane the value wraps rather than clipping, and below @xs the row stacks so the
value gets the full width. An unset
property renders a muted placeholder — an honest "nothing here yet" affordance instead of a blank row
a reader has to interpret.
- Domains
- Name
- Team
Inline rows and sections
variant="inline" is the record-aside row for a narrow rail card: icon + label in a fixed 112px
column (a long label truncates, its full text in the tooltip), and the value left-aligned in the
second column, so every value starts at the same x — as in Linear or Notion. An editable value is a
ghost Record Chip (variant="ghost"): PropertyValue
hangs its padding past the column's start and its 24px height outside the line, so its text lines up
with plain values and every row is the same height. An editable text value is an inline
Editable Cell: PropertyValue gives it the same compact box — 6px
and 2px of padding hung outside by equal negative margins — so its text keeps the plain values' left
edge, baseline and row height at rest, on hover and while editing, and the tint sits just around it. An unset value is the chip's muted placeholder
("Set due date"), or PropertyEmpty ("—") when it cannot be edited. PropertySection puts a small muted title over values too big for a row (chips,
people). The rail's other value shapes — an avatar, avatar rows with "+N", a long value clamped to
three lines — are in Record Aside.
Two columns
columns={2} flows the rows into two columns, left to right, once the list's own container is
at least @xl (36rem) wide — a wide record pane reads as two columns of facts — and keeps one
column in a narrower pane, whatever the viewport. It works with both variants: inline keeps its
fixed label column inside each column. The third list below is the same inline list in a 288px
rail, back to one column.
- Type
- Brand
- SKU
- Lead time
- Finish
- Origin
- Owner
- Updated
- Type
- Brand
- SKU
- Lead time
- Finish
- Origin
- Owner
- Updated
- Type
- Brand
- SKU
- Lead time
- Finish
- Origin
- Owner
- Updated
API Reference
PropertyList
| Prop | Type | Default | Description |
|---|---|---|---|
columns | 1 | 2 | 1 | 2 flows the rows into two columns, left to right, once the list's own container is wide
enough (@xl, 36rem) — a wide record pane reads as two columns of facts, and the same list in
a narrow rail stays one column. Works with both variants; inline keeps its fixed label
column inside each column. |
variant | "inline" | "stacked" | "stacked" | stacked — the label column beside the value, stacking when the pane is narrow. inline —
the record-aside row for a narrow rail card: a fixed 112px label column (a long label
truncates, its full text in the tooltip), so every value starts at the same x, left-aligned,
as in Linear or Notion; its editable values are quiet pickers — RecordChip variant="ghost". |
Data attributes and CSS variables on PropertyList
| Attribute | Values |
|---|---|
data-columns | mirrors a prop or state value |
data-slot | "property-list" |
data-variant | mirrors a prop or state value |
PropertyRow
PropertyRow accepts native <div> props and groups one <dt>/<dd> pair.
PropertyLabel
| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | Leading inline-role icon (decorative — the text carries the meaning). |
Data attributes and CSS variables on PropertyLabel
| Attribute | Values |
|---|---|
data-slot | "property-label" |
PropertyValue
PropertyValue accepts native <dd> props and forwards its ref to the value cell.
PropertyEmpty
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | "—" | What an unset value shows. Pass a quiet "+ Add" picker trigger instead when the value is editable. |
Data attributes and CSS variables on PropertyEmpty
| Attribute | Values |
|---|---|
data-slot | "property-empty" |
PropertySection
| Prop | Type | Default | Description |
|---|---|---|---|
title* | React.ReactNode | — | The section's small muted title, e.g. "Participants" or "Linked to". |
Data attributes and CSS variables on PropertySection
| Attribute | Values |
|---|---|
data-slot | "property-section" | "property-section-title" |
Accessibility
- Native
<dl>,<dt>, and<dd>semantics expose each label/value relationship. - Label icons are decorative; always keep a visible text label.
- Links and controls inside values keep their native keyboard behavior and focus indicator.
- Source order remains label then value in both LTR and RTL layouts.
- Values do not clip their overflow, so a link inside one keeps its full focus outline.
| Key | Action |
|---|---|
| Tab | Move through links and controls inside property values. |
| Enter | Activate the focused link or control. |
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | labeled, semantic-html |
| Visual | default |
Do / Don't
Recent Strip
One row of the records a person opened last, as compact cards in a sideways strip with edge fades and arrow keys, and a single line of chips on a phone.
Record Aside
The cards of a record page's right rail — titled sections of inline properties, people, linked records and full-width action rows.