Trang này chưa có bản tiếng Việt. Nội dung dưới đây là bản tiếng Anh.
Components
Item
A flexible list-row primitive that composes media, title, description, and action slots into a single aligned row — the building block for settings lists, file rows, and feeds.
Basic usage
Compose ItemMedia, ItemContent, and ItemActions inside an Item.
Updated 3 days ago · 2.4 MB
Variants
Three surface treatments — default for in-list rows, outline for standalone cards, and muted for inactive state.
Transparent — sits flush in a list.
Bordered — reads as a standalone card.
Soft fill — useful for inactive rows.
Media slots
ItemMedia renders a leading affordance: icon boxes a Lucide glyph, while image clips an avatar or thumbnail.
A boxed 32px icon affordance.
A 40px avatar or thumbnail.
Grouped lists
Wrap rows in an ItemGroup (a role="list" container) and divide them with ItemSeparator. Use asChild to make a whole row a link. The group gives each Item its listitem role, except an asChild row — so the rows here, which are links, are each wrapped in one; see List roles.
Header and footer bands
Item is a flex-wrap row, and ItemHeader and ItemFooter are basis-full — they take a full line of their own above and below the media/content/actions line, with their children pushed to opposite ends. Use them for metadata that belongs to the row but does not belong beside the title.
Due 31 March · $4,200
Props
Item — the row container.
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
variant | 'default' | 'outline' | 'muted' | 'default' | Surface treatment of the row — transparent, bordered, or soft-filled with bg-muted/50. |
size | 'default' | 'sm' | 'default' | default is p-4 gap-4; sm tightens the gap to 2.5 and takes its vertical padding from the --list-item-py token, so it follows the density axis. |
asChild | boolean | false | Merge props onto the child element instead of rendering a div — use to make the whole row a link or button. |
className | string | — | Additional Tailwind classes to merge. |
...rest | React.ComponentProps<'div'> | — | All native div props are forwarded. |
ItemMedia — the leading affordance.
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
variant | 'default' | 'icon' | 'image' | 'default' | default is unstyled; icon renders a 32px boxed slot; image renders a 40px clipped, rounded slot for an <img>. |
className | string | — | Additional Tailwind classes to merge. |
...rest | React.ComponentProps<'div'> | — | All native div props are forwarded. |
ItemSeparator — a preset Separator, so it takes the same props.
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | Forwarded to Separator. ItemSeparator presets it to horizontal because it divides stacked rows; pass it explicitly only if you really need a vertical rule. |
decorative | boolean | true | Forwarded to Separator — leave it true so the rule is skipped by screen readers and the list role stays clean. |
className | string | — | Merged after the built-in my-0, so a margin override wins. |
The remaining slots (ItemGroup, ItemContent, ItemTitle, ItemActions, ItemHeader, ItemFooter) forward native <div> props only; ItemDescription renders a <p>. The itemVariants and itemMediaVariants cva helpers are exported for building a row that has to match without being an Item.
List roles
ItemGroup renders role="list", and since 3.7.0 it gives role="listitem" to every Item among its children — through arrays and fragments, so a .map() with separators needs nothing from you. A list without list items is invalid (axe reports aria-required-children) and a screen reader can announce it as empty, which is why the group does it rather than leaving it to every app.
It works with cloneElement, not a context, so Item stays a static file that renders in a Server Component. The price is that it only sees an Item written directly in the tree it is given. Three cases are left to you: an Item drawn by a component of your own — the group sees your component, not the Item inside it — so pass role="listitem" there; an Item that already has a role, which the group leaves alone; and an asChild row that renders an <a>, where a listitem role would replace the link role — wrap it in <div role="listitem">, as the grouped list above does. Before 3.7.0 every row needed the role by hand; one passed to an Item directly inside the group is now redundant and harmless.
import { Item, ItemContent, ItemGroup, ItemSeparator, ItemTitle } from '@comitor/ui'
// ItemGroup is role="list" and, since 3.7.0, gives role="listitem" to every
// Item it can see among its children — through arrays and fragments.
type Invoice = { id: string; number: string }
export function Example({ invoices }: { invoices: Invoice[] }) {
return (
<ItemGroup>
{/* A plain row: no role to pass, the group adds it. */}
<Item size="sm">
<ItemContent>
<ItemTitle>Invoices</ItemTitle>
</ItemContent>
</Item>
<ItemSeparator />
{/* A row drawn by your own component: the group only sees <InvoiceRow>,
not the Item inside it, so that Item passes the role itself. */}
{invoices.map((invoice) => (
<InvoiceRow key={invoice.id} invoice={invoice} />
))}
<ItemSeparator />
{/* An asChild row renders your <a>: role="listitem" there would replace
its link role, so the role goes on a wrapper instead. */}
<div role="listitem">
<Item asChild size="sm">
<a href="/billing">
<ItemContent>
<ItemTitle>Billing</ItemTitle>
</ItemContent>
</a>
</Item>
</div>
</ItemGroup>
)
}
function InvoiceRow({ invoice }: { invoice: Invoice }) {
return (
<Item role="listitem" size="sm">
<ItemContent>
<ItemTitle>{invoice.number}</ItemTitle>
</ItemContent>
</Item>
)
}Accessibility
ItemGroupappliesrole="list"and gives eachItemamong its childrenrole="listitem". Supply it yourself on anItemdrawn by your own component, and on a<div role="listitem">wrapper for anasChildrow — see List roles.- Use
asChildwith an<a>or<button>so the interactive row is a real, focusable element with native keyboard support. - Interactive rows receive a
focus-visiblering using the brand gold ring token, visible on keyboard navigation and hidden on click. - Decorative media (icons, avatars) should be hidden from screen readers — give
<img>an emptyalt=""and letItemTitlecarry the accessible name. ItemTitleis a<div>, not a heading — a long list of rows should not add dozens of entries to the document outline. If a row genuinely is a section heading, render one inside it.ItemDescriptionis clamped to two lines, so never put information there that has no other home. Links inside it are underlined, not colour-only.