Trang này chưa có bản tiếng Việt. Nội dung dưới đây là bản tiếng Anh.

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.

Quarterly report

Variants

Three surface treatments — default for in-list rows, outline for standalone cards, and muted for inactive state.

Default
Outline
Muted

Media slots

ItemMedia renders a leading affordance: icon boxes a Lucide glyph, while image clips an avatar or thumbnail.

Icon media
Image media

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.

Invoice #1042Pending
March retainer
Sent 2 days agoNet 30

Props

Item — the row container.

PropKiểuMặc địnhMô 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.
asChildbooleanfalseMerge props onto the child element instead of rendering a div — use to make the whole row a link or button.
classNamestring—Additional Tailwind classes to merge.
...restReact.ComponentProps<'div'>—All native div props are forwarded.

ItemMedia — the leading affordance.

PropKiểuMặc địnhMô 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>.
classNamestring—Additional Tailwind classes to merge.
...restReact.ComponentProps<'div'>—All native div props are forwarded.

ItemSeparator — a preset Separator, so it takes the same props.

PropKiểuMặc địnhMô 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.
decorativebooleantrueForwarded to Separator — leave it true so the rule is skipped by screen readers and the list role stays clean.
classNamestring—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.

item-group-roles.tsx
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

  • ItemGroup applies role="list" and gives each Item among its children role="listitem". Supply it yourself on an Item drawn by your own component, and on a <div role="listitem"> wrapper for an asChild row — see List roles.
  • Use asChild with an <a> or <button> so the interactive row is a real, focusable element with native keyboard support.
  • Interactive rows receive a focus-visible ring 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 empty alt="" and let ItemTitle carry the accessible name.
  • ItemTitle is 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.
  • ItemDescription is clamped to two lines, so never put information there that has no other home. Links inside it are underlined, not colour-only.