Skip to content
Component installs need the registry setup
VegaStack Design

Item

A flex row for content — media, title, description and actions — in three variants and three sizes, groupable with dividers.

Status
stable
Since
0.1.0
Accessibility pattern
content row, not a control

Last updated

Basic Item

A simple item with title and description.

Your profile has been verified.

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 item

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/item

Run 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>;
Basic Item

A simple item with title and description.

Your profile has been verified.

Anatomy

Item — data-slot="item-listitem"
ItemActions — data-slot="item-actions"
ItemContent — data-slot="item-content"
ItemDescription — data-slot="item-description"
ItemFooter — data-slot="item-footer"
ItemGroup — data-slot="item-group"
ItemGroupLabel
ItemHeader — data-slot="item-header"
ItemMedia — data-slot="item-media"
ItemSeparator — data-slot="item-separator"
ItemTitle — data-slot="item-title"

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.

ItemHeader — spans the row
ItemTitle

ItemMedia, ItemContent and ItemActions share one flex row.

ItemFooter — spans the 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.

Item — content

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.

Default Variant

Transparent background with no border.

Outline Variant

Outlined style with a visible border.

Muted Variant

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).

Default Size

The standard size for most use cases.

Small Size

A compact size for dense layouts.

Extra Small Size

The most compact size available.

Icon

Use ItemMedia with variant="icon" to display an icon.

Security Alert

New login detected from unknown device.

Avatar

Use the default ItemMedia to display an avatar, or a stack of them.

GH
Grace Hopper

Last seen 5 months ago

GH
No Team Members

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.

AL
ada

ada@vegastack.com

LT
linus

linus@vegastack.com

GH
grace

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>

Use ItemHeader to add a header above the item content — a cover image, for instance.

vs-1.5-sm

Everyday tasks and UI generation.

vs-1.5-lg

Advanced thinking or reasoning.

vs-2.0-mini

Open source model for everyone.

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.

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.

Basic Item

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.

Getting started
x
Complete your profile
Invite your team
Connect your calendar
Create your first report

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

Support inbox

Archived

Security review

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

PropTypeDefaultDescription
highlightedboolean—
size"default" | "sm" | "xs" | null—
variant"default" | "muted" | "outline" | null—
PropTypeDefaultDescription
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).

PropTypeDefaultDescription
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

  • Item is a div by default and carries no role of its own — it is content, not a control. Pass render to make it an anchor or a button when the whole row is activatable.
  • ItemGroup renders role="list", which admits only listitem children. Every Item inside it takes role="listitem" through context; a row rendered as a link or a button keeps its own role and gets a listitem wrapper instead. ItemSeparator is hidden with aria-hidden — the list already conveys the separation, and an exposed role="separator" there is a critical aria-required-children failure.
  • ItemGroupLabel names the group after it through aria-labelledby, so the list is announced with its heading.
  • A row that owns an accessible description (a command option, a menu item) provides ItemDescriptionContext; an ItemDescription inside it registers its id, and the row points aria-describedby at it, so the second line is read as a description rather than as part of the name.
  • On :focus-visible the global 2px outline in base.css applies (FOC-1). The item's recipe carries no focus ring, no ring-3 glow and no outline-none of its own (FOC-6).
  • ItemTitle never 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 ItemActions has no visible text — give it an aria-label.
ContractStates tested
Behaviourdefault
Accessibilitysemantic-html
Visualdefault, hover

Do / Don't

Do
Pass the render prop an anchor when the whole row navigates, so one element owns the link.
Don't
Nest a button inside an Item that is already a link — the two activation targets overlap.

Deviations

Upstream's file plus packages/ui/upstream/patches/item.patch. Every hunk:

  • API-29 — Item takes highlighted: data-highlighted and the bg-accent wash eased in over duration-slow, the "just changed" flash.
  • A11Y-7 — ItemGroup licenses role="listitem" on the items inside it, through context, and hides the separator's rule from assistive tech. role="list" admits only listitem children, so upstream's own composition is a critical aria-required-children violation as shipped. A row rendered as a link or a button keeps its own role and the listitem moves to a wrapper, and its truncated cells are not tab stops of their own.
  • API-16 — "use client" is added: useRender calls React.useRef internally, so the module throws on import in a React Server Component without the boundary.
  • API-19 — ItemDescriptionContext and useItemDescriptionId() let a row link its ItemDescription as its accessible description. Outside such a row the description renders as upstream's.
  • API-20 — ItemGroupLabel, a heading part that names the ItemGroup after it.
  • DOC-2 — cn is imported from @vegastack/design, and the file carries the registry provenance header.
  • FOC-1, FOC-6 — itemVariants loses upstream's outline-none, focus-visible:border-ring and 3px focus-visible:ring-ring/50 glow, so the global 2px outline is what a focused item shows.
  • LAY-11 — ItemTitle loses line-clamp-1, which upstream sets on the same element as flex. Truncation goes on an inner span instead.
  • API-37 — ItemGroup takes variant (default · flush): flush lays rows with no gap and bleeds them into the enclosing card's padding. MK, 2026-10-26.

On this page