Item
FreeA generic row: media, title, description, meta and an action. It uses the data table's row spacing and its hover and selected fills.
pnpm dlx shadcn@latest add https://caliperui.site/r/item.jsonWhen to use #
Use it for
- Settings lists, search results, files and people.
- The row inside a menu or a panel.
Skip it when
- Tabular data with columns to compare; use the data table.
- A row with a link and a button side by side; make the row the link.
Decisions #
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- Long text is cut, the action stays
- Title and description truncate to one line so the action never moves.
Styling
- The table's row, in black and white
- Padding and hover match the data table. The selected row is a neutral fill with a 2px ink edge, so a list never turns orange or brown.
Usage
- A row that goes somewhere is one link
- Use asChild with a link. A link and a button in the same row give two tab stops for one idea.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/item.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above. It is built from Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemMeta and ItemTitle.
import { Bell, ChevronRight, FileText, Link2 } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemMeta, ItemTitle } from "@/components/ui/item" export function ItemDemo() { return ( <div className="grid w-full max-w-md gap-6"> <ItemGroup> <Item asChild> <a href="#forma"> <ItemMedia>FS</ItemMedia> <ItemContent> <ItemTitle>Forma Studio</ItemTitle> <ItemDescription>14 projects · Owner</ItemDescription> </ItemContent> <ItemMeta>Active now</ItemMeta> <ChevronRight data-mirror="" aria-hidden="true" className="size-4 text-muted-foreground" /> </a> </Item> <Item asChild current> <a href="#ines"> <ItemMedia round>IR</ItemMedia> <ItemContent> <ItemTitle>Inês Duarte</ItemTitle> <ItemDescription>ines@halden.app · Customer team</ItemDescription> </ItemContent> <Badge>Selected</Badge> </a> </Item> <Item asChild> <a href="#notifications"> <ItemMedia><Bell aria-hidden="true" /></ItemMedia> <ItemContent> <ItemTitle>Notifications</ItemTitle> <ItemDescription>Email on invoices, push for approvals</ItemDescription> </ItemContent> <ChevronRight data-mirror="" aria-hidden="true" className="size-4 text-muted-foreground" /> </a> </Item> <Item disabled> <ItemMedia><Link2 aria-hidden="true" /></ItemMedia> <ItemContent> <ItemTitle>Webhooks</ItemTitle> <ItemDescription>Available on the Team plan</ItemDescription> </ItemContent> </Item> </ItemGroup> <ItemGroup separate> <Item> <ItemMedia><FileText aria-hidden="true" /></ItemMedia> <ItemContent> <ItemTitle>A very long file name that should truncate instead of pushing the action off the row.pdf</ItemTitle> <ItemDescription>Uploaded by Tom · 2.4 MB</ItemDescription> </ItemContent> <Button size="sm" variant="secondary">Download</Button> </Item> </ItemGroup> </div> ); }
API reference #
Item
component
Also accepts the native props of its root element and a ref.
ItemContent
component
No props of its own; it accepts the native props of its root element.
ItemDescription
component
No props of its own; it accepts the native props of its root element.
ItemGroup
component
Also accepts the native props of its root element and a ref.
ItemMedia
component
Also accepts the native props of its root element and a ref.
ItemMeta
component
No props of its own; it accepts the native props of its root element.
ItemTitle
component
No props of its own; it accepts the native props of its root element.
Accessibility #
It exposes list semantics, with aria-current and aria-disabled kept in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.