Item
A flex row for content — media, title, description and actions — in three variants and three sizes, groupable with dividers.
- Status
- Since
0.1.0- Accessibility pattern
- content row, not a control
Last updated
A simple item with title and description.
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 Item before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify itemInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/itemRun 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.
The same command installs the registry items it composes: @vegastack/separator, @vegastack/truncated-text.
Usage
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item";
<Item>
<ItemMedia variant="icon">
<InboxIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button>Action</Button>
</ItemActions>
</Item>;A simple item with title and description.
Anatomy
Examples
Composition
ItemMedia, ItemContent and ItemActions share one flex row; ItemHeader and ItemFooter take
a full basis, so they sit above and below that row inside the same item.
ItemMedia, ItemContent and ItemActions share one flex row.
Item vs Field
Use Field when you need to display a form input such as a checkbox, input, radio or select — it
owns the label association, the description and the error. Use Item when you only need to display
content: a title, a description and actions.
A title, a description and actions. Nothing is submitted.
Reach for Field whenever a control has to be labelled and validated.
Variant
Use the variant prop to change the visual style of the item: default, outline or muted.
Transparent background with no border.
Outlined style with a visible border.
Muted background for secondary content.
Size
Use the size prop to change the size of the item. Available sizes are default, sm and xs.
An enclosing ItemGroup tightens its own gap to match. The tier steps the padding and the gaps;
of the parts inside, only ItemMedia shrinks — the title and description keep their type size
(xs alone drops the description to text-xs).
The standard size for most use cases.
A compact size for dense layouts.
Icon
Use ItemMedia with variant="icon" to display an icon.
New login detected from unknown device.
Avatar
Use the default ItemMedia to display an avatar, or a stack of them.
Last seen 5 months ago
Invite your team to collaborate on this project.
Image
Use ItemMedia with variant="image" to display an image — a fixed square that clips its corners
and tracks the item's size.
Group
Use ItemGroup to group related items, and ItemSeparator to rule between them. The group renders
role="list", so each item carries role="listitem" and the rule between two rows is hidden from
assistive technology.
ada@vegastack.com
linus@vegastack.com
grace@vegastack.com
variant="flush" is the group for rows inside a card: no gap between rows, and the rows bleed
into the card's padding so their text lines up with the card's own content.
<Card>
<CardContent>
<ItemGroup variant="flush">…</ItemGroup>
</CardContent>
</Card>Header
Use ItemHeader to add a header above the item content — a cover image, for instance.
Everyday tasks and UI generation.
Advanced thinking or reasoning.
Open source model for everyone.
Link
Use the render prop to render the item as a link. The hover state applies to the anchor, and the
focus outline is the global one — the item's own recipe carries no ring.
Dropdown
At size="xs" an item drops its padding inside a dropdown menu, so it composes as a rich menu row.
RTL
Every gap and padding is written in logical properties, so a right-to-left document mirrors the media, content and actions columns without any extra work.
A simple item with title and description.
عنصر بسيط يحتوي على عنوان ووصف.
Checklist
A getting-started checklist is a composition, not a component: a Progress over an ItemGroup of
Item size="sm" rows, each led by a StatusIcon that names its state. The host owns which steps
are done.
Link rows in a group
An Item rendered as a link or a button inside an ItemGroup keeps its own role: the listitem
the group requires moves to a <div role="listitem" data-slot="item-listitem"> wrapper around it.
A RelativeTime or truncated cell inside such a row is not a tab stop of its own — the row is the
one control.
Grouped with labels
ItemGroupLabel is a small heading (an h3 by default; pass render={<h2 />} for another level)
that names the ItemGroup rendered immediately after it, so each group is announced as a named
list. The name is linked after mount; to name the list in server-rendered HTML as well, give the
label an id and the group aria-labelledby yourself.
Pinned
Archived
Highlighted
highlighted flashes a row the user just created or changed: the accent wash eases in and the row
is marked data-highlighted. Clear the flag after a moment to return it to rest — never hand-write
data-highlighted:bg-accent. (DataList rows take the same state through highlightedIds or
rowProps.)
<Item highlighted={justChanged.has(task.id)}>…</Item>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
highlighted | boolean | — | |
size | "default" | "sm" | "xs" | null | — | |
variant | "default" | "muted" | "outline" | null | — |
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "icon" | "image" | null | — |
ItemContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemTitle adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemDescription adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemActions adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "flush" | "default" | flush (API-37): rows with no gap between them, bled into the enclosing card's padding so
a row's text lines up with the card's own content — a list of rows inside a card. |
ItemGroupLabel adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemSeparator adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemHeader adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ItemFooter adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Accessibility
Itemis adivby default and carries no role of its own — it is content, not a control. Passrenderto make it an anchor or a button when the whole row is activatable.ItemGrouprendersrole="list", which admits onlylistitemchildren. EveryIteminside it takesrole="listitem"through context; a row rendered as a link or a button keeps its own role and gets alistitemwrapper instead.ItemSeparatoris hidden witharia-hidden— the list already conveys the separation, and an exposedrole="separator"there is a criticalaria-required-childrenfailure.ItemGroupLabelnames the group after it througharia-labelledby, so the list is announced with its heading.- A row that owns an accessible description (a command option, a menu item) provides
ItemDescriptionContext; anItemDescriptioninside it registers itsid, and the row pointsaria-describedbyat it, so the second line is read as a description rather than as part of the name. - On
:focus-visiblethe global 2px outline inbase.cssapplies (FOC-1). The item's recipe carries no focus ring, noring-3glow and nooutline-noneof its own (FOC-6). ItemTitlenever clamps on the element that is also a flex container; wrap the text in an inner span when it has to truncate (LAY-11).- An icon-only action inside
ItemActionshas no visible text — give it anaria-label.
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | semantic-html |
| Visual | default, hover |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/item.patch. Every hunk:
- API-29 —
Itemtakeshighlighted:data-highlightedand thebg-accentwash eased in overduration-slow, the "just changed" flash. - A11Y-7 —
ItemGrouplicensesrole="listitem"on the items inside it, through context, and hides the separator's rule from assistive tech.role="list"admits onlylistitemchildren, so upstream's own composition is a criticalaria-required-childrenviolation as shipped. A row rendered as a link or a button keeps its own role and thelistitemmoves to a wrapper, and its truncated cells are not tab stops of their own. - API-16 —
"use client"is added:useRendercallsReact.useRefinternally, so the module throws on import in a React Server Component without the boundary. - API-19 —
ItemDescriptionContextanduseItemDescriptionId()let a row link itsItemDescriptionas its accessible description. Outside such a row the description renders as upstream's. - API-20 —
ItemGroupLabel, a heading part that names theItemGroupafter it. - DOC-2 —
cnis imported from@vegastack/design, and the file carries the registry provenance header. - FOC-1, FOC-6 —
itemVariantsloses upstream'soutline-none,focus-visible:border-ringand 3pxfocus-visible:ring-ring/50glow, so the global 2px outline is what a focused item shows. - LAY-11 —
ItemTitlelosesline-clamp-1, which upstream sets on the same element asflex. Truncation goes on an inner span instead. - API-37 —
ItemGrouptakesvariant(default·flush):flushlays rows with no gap and bleeds them into the enclosing card's padding. MK, 2026-10-26.