Components
A versatile flex container for displaying content with media, title, description, and actions.
Playground
Updated two days ago — 2.4 MB
12 new updates from your team.
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@workspace/ui/components/item"
<Item variant="outline">
<ItemMedia variant="icon">
<TrayIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Weekly digest</ItemTitle>
<ItemDescription>12 new updates from your team.</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="outline">
Open
</Button>
</ItemActions>
</Item>default is transparent with no border, outline draws the border token, and muted fills with bg-muted/50 for secondary content.Transparent background with no border.
Outlined style with a visible border.
Muted background for secondary content.
<Item>
<ItemMedia variant="icon">
<TrayIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Default Variant</ItemTitle>
<ItemDescription>Transparent background with no border.</ItemDescription>
</ItemContent>
</Item>
<Item variant="outline">…</Item>
<Item variant="muted">…</Item>xs size also drops the description to text-xs and collapses the content gap — made for dense lists and menus.The standard size for most use cases.
A compact size for dense layouts.
<Item variant="outline">…</Item> <Item variant="outline" size="sm">…</Item> <Item variant="outline" size="xs">…</Item>
ItemMedia variant="icon" sizes any unsized SVG to size-4. When the item has a description, the media nudges down and aligns to the first line instead of centering against the whole block.New login detected from unknown device.
<Item variant="outline">
<ItemMedia variant="icon">
<ShieldWarningIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Security Alert</ItemTitle>
<ItemDescription>New login detected from unknown device.</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="outline">
Review
</Button>
</ItemActions>
</Item>Last seen 5 months ago
Invite your team to collaborate on this project.
<Item variant="outline">
<ItemMedia>
<Avatar className="size-10">
<AvatarImage src="https://github.com/evilrabbit.png" />
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>Evil Rabbit</ItemTitle>
<ItemDescription>Last seen 5 months ago</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="icon-sm" variant="outline" className="rounded-full">
<PlusIcon />
</Button>
</ItemActions>
</Item>variant="image" crops the media into a size-10 rounded thumbnail — size-8 at sm and size-6 at xs. A second ItemContent turns flex-none automatically for trailing metadata like durations.<ItemGroup className="gap-4">
{music.map((song) => (
<Item key={song.title} variant="outline" asChild role="listitem">
<a href="#">
<ItemMedia variant="image">
<Image
unoptimized
src={`https://avatar.vercel.sh/${song.cover}`}
alt={song.title}
width={40}
height={40}
/>
</ItemMedia>
<ItemContent>
<ItemTitle>
{song.title} —{" "}
<span className="text-muted-foreground">{song.album}</span>
</ItemTitle>
<ItemDescription>{song.artist}</ItemDescription>
</ItemContent>
<ItemContent className="flex-none text-center">
<ItemDescription>{song.duration}</ItemDescription>
</ItemContent>
</a>
</Item>
))}
</ItemGroup>role="list" and stacks items in a column — the gap tightens automatically when it contains sm or xs items. ItemSeparator draws a Separator between rows for bordered list layouts.shadcn@vercel.com
maxleiter@vercel.com
evilrabbit@vercel.com
<ItemGroup>
{people.map((person, index) => (
<React.Fragment key={person.username}>
<Item role="listitem">
<ItemMedia>
<Avatar>
<AvatarImage src={person.avatar} className="grayscale" />
<AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{person.username}</ItemTitle>
<ItemDescription>{person.email}</ItemDescription>
</ItemContent>
<ItemActions>
<Button variant="ghost" size="icon" className="rounded-full">
<PlusIcon />
</Button>
</ItemActions>
</Item>
{index < people.length - 1 && <ItemSeparator />}
</React.Fragment>
))}
</ItemGroup>basis-full) above the content — wrapping the flex container into a card-like layout. ItemFooter does the same below. Combine with a grid ItemGroup for galleries.Everyday tasks and UI generation.
Advanced thinking or reasoning.
Open source model for everyone.
<ItemGroup className="grid grid-cols-3 gap-4">
{models.map((model) => (
<Item key={model.name} variant="outline" role="listitem">
<ItemHeader>
<Image
unoptimized
src={`https://avatar.vercel.sh/${model.name}`}
alt={model.name}
width={128}
height={128}
className="aspect-square w-full rounded-sm object-cover"
/>
</ItemHeader>
<ItemContent>
<ItemTitle>{model.name}</ItemTitle>
<ItemDescription>{model.description}</ItemDescription>
</ItemContent>
</Item>
))}
</ItemGroup>asChild renders the item as its child anchor — the whole row becomes clickable, tints with bg-muted on hover, and shows the ring tokens on keyboard focus.<Item asChild>
<a href="#">
<ItemContent>
<ItemTitle>Visit our documentation</ItemTitle>
<ItemDescription>
Learn how to get started with our components.
</ItemDescription>
</ItemContent>
<ItemActions>
<CaretRightIcon className="size-4" />
</ItemActions>
</a>
</Item>
<Item variant="outline" asChild>
<a href="#" target="_blank" rel="noopener noreferrer">
<ItemContent>
<ItemTitle>External resource</ItemTitle>
<ItemDescription>
Opens in a new tab with security attributes.
</ItemDescription>
</ItemContent>
<ItemActions>
<ArrowSquareOutIcon className="size-4" />
</ItemActions>
</a>
</Item>xs size zeroes its padding inside a dropdown-menu-content, so an Item slots into DropdownMenuItem without doubling the menu padding — rich menu rows with avatar, title, and description.<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
Select <CaretDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start">
<DropdownMenuGroup>
{people.map((person) => (
<DropdownMenuItem key={person.username}>
<Item size="xs" className="w-full p-2">
<ItemMedia>
<Avatar className="size-6">
<AvatarImage src={person.avatar} className="grayscale" />
<AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent className="gap-0">
<ItemTitle>{person.username}</ItemTitle>
<ItemDescription className="leading-none">
{person.email}
</ItemDescription>
</ItemContent>
</Item>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>dir="rtl" ancestor mirrors media, content, and actions without extra classes — descriptions stay start-aligned via text-start.12 تحديثًا جديدًا من فريقك.
<div dir="rtl">
<Item variant="outline">
<ItemMedia variant="icon">
<TrayIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>الملخص الأسبوعي</ItemTitle>
<ItemDescription>12 تحديثًا جديدًا من فريقك.</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="outline">
فتح
</Button>
</ItemActions>
</Item>
</div>role="list", but Item stays a plain div — add role="listitem" to items inside a group so assistive tech announces the list structure. Interactive items (asChild links) carry a visible focus-visible ring; icon-only action buttons need an aria-label, and media images need meaningful alt text. Note that ItemTitle clamps to one line and ItemDescription to two — don't hide essential information in the truncated overflow.<ItemGroup>
<Item role="listitem" asChild>
<a href="/notifications">
<ItemContent>
<ItemTitle>Notifications</ItemTitle>
</ItemContent>
<ItemActions>
<Button size="icon-sm" variant="ghost" aria-label="Dismiss">
<XIcon />
</Button>
</ItemActions>
</a>
</Item>
</ItemGroup>| Component | Prop | Default |
|---|---|---|
| Item | variant?: "default" | "outline" | "muted" | "default" |
| Item | size?: "default" | "sm" | "xs" | "default" |
| Item | asChild?: boolean | false |
| ItemMedia | variant?: "default" | "icon" | "image" | "default" |
| ItemSeparator | ...props: React.ComponentProps<typeof Separator> | — |
<ItemGroup data-slot="item-group" role="list" /> <ItemSeparator data-slot="item-separator" /> <Item data-slot="item" data-variant="default|outline|muted" data-size="default|sm|xs" /> <ItemMedia data-slot="item-media" data-variant="default|icon|image" /> <ItemContent data-slot="item-content" /> <ItemTitle data-slot="item-title" /> <ItemDescription data-slot="item-description" /> <ItemActions data-slot="item-actions" /> <ItemHeader data-slot="item-header" /> <ItemFooter data-slot="item-footer" />
Tokens used
10 design tokens consumed by the Item component.
--border
Border
--muted
Muted
--muted-foreground
Muted Foreground
--primary
Primary
--ring
Ring
--text-sm
Text Small (14px)
--text-xs
Text Extra Small (12px)
--radius-md
Radius 0.8× (8px)
--radius-sm
Radius 0.6× (6px)
--spacing
Spacing unit (4px)