A <ul>/<ol> with custom, token-coloured markers and a controllable item gap, marker gap and indent — the styled counterpart to the bare list styles in primitiv.reset.
Registry-only
No headless primitive — this component ships only as a copied styled file, so there is no Headless mode. primitiv add list installs it whichever mode you are reading.
Playground
Preview
Ships with zero styles
Accessible by construction
Yours to restyle
import{List}from"@/components/ui/list";<Listtype="unordered"size="md"><List.Item>Ships with zero styles</List.Item><List.Item>Accessible by construction</List.Item><List.Item>Yours to restyle</List.Item></List>
Density is set by a data-density ancestor — the Context system, not a List prop.
Installation
npx primitiv add list
pnpm dlx primitiv add list
yarn dlx primitiv add list
bunx primitiv add list
Import
import{List}from"@/components/ui/list";
No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Anatomy
Two parts: List renders the <ul> or <ol> (per type) and owns the marker/indent/size knobs; List.Item renders each <li>.
<List><List.Item/></List>
Props
Generated from the copied file’s props type and contract.json. Never hand-maintained.
List
Extends HTMLUListElement — every native attribute of that element is accepted and forwarded.
Prop
Type
Default
Description
ref
Ref<HTMLUListElement | HTMLOListElement>
—
List renders either a <ul> or an <ol> depending on type, so its
ref can resolve to either element.
type
"unordered" | "ordered"
unordered
Marker style.
marker
"true" | "false"
true
Whether each item draws its leading bullet/number. false is the equivalent of list-style: none — the marker's box and its gap both go, so rows sit flush — for prose and landing-page lists where bullets read as noise. Independent of indent; combine both false for a fully flush list. Mirrors the Figma List set's Marker axis and ListItem's Show marker boolean.
indent
"true" | "false"
true
Whether the list applies its own left indent, or sits flush for inline/nested contexts where a parent already supplies it.
size
"xs" | "sm" | "md" | "lg" | "xl"
md
Item text size; data-density scales each size further.
List.Item
Headless only — the copied file renders this part for you and exports no separate component, so there is nothing to import under Styled.
Extends HTMLLIElement — every native attribute of that element is accepted and forwarded.
Prop
Type
Default
Description
disabled
boolean
false
Dims the row to mark the entry as unavailable, matching the Figma
ListItem set's State=disabled variant (RFC 0012 D9 — 50% opacity over
the whole row, marker included).
Presentational only. An <li> isn't interactive, so this publishes the
data-disabled styling hook and nothing else — it does not remove
anything focusable inside the row from the tab order, which stays the
consumer's job.
Styling contract
9 CSS custom properties on .primitiv-list — mode-agnostic. These names are the stable surface; the values are not.
Emitted automatically by the headless primitive — style against these rather than adding your own state classes. Grouped by the part that emits them, since most are emitted by more than one.
List.Item
Headless only — the copied file renders this part for you and exports no separate component, so there is nothing to import under Styled. Target it with the class below.
className:.primitiv-list__item
Attribute
Value
When
data-disabled
""
disabled
Accessibility
List renders a real <ul> or <ol> and List.Item a real <li>, so assistive tech announces the item count and position — dropping the marker with marker={false} changes the look, never the semantics.
The markers are drawn by the component rather than list-style, so they take the type colour and stay aligned, but they remain decoration on top of the native list structure.
List.Item accepts disabled, which dims the row (50% opacity) as a visual state — it is presentational here, since a list item is not itself a control.
Examples
Every example below reacts to the density control. Currently showing Styled mode.
Ordered and unordered
type picks the element and the marker: unordered is a <ul> with token-coloured bullets, ordered an <ol> with numbers. The markers are drawn by the component, not the browser default, so they inherit the type colour and sit on the baseline.
Ships with zero styles
Accessible by construction
Yours to restyle
Run the CLI
Copy the files
Edit them
import{List}from"@/components/ui/list";<Listtype="ordered"><List.Item>Run the CLI</List.Item><List.Item>Copy the files</List.Item><List.Item>Edit them</List.Item></List>
A flush prose list
marker={false} drops the bullet (the equivalent of list-style: none, gap and all) and indent={false} removes the inset — combine both for a list that reads as plain lines, for prose or a nav, while staying a real <ul> so its semantics are unchanged. The two are independent.
Ships with zero styles
Accessible by construction
Yours to restyle
import{List}from"@/components/ui/list";<Listmarker={false}indent={false}><List.Item>Ships with zero styles</List.Item><List.Item>Accessible by construction</List.Item></List>