Gesso
Layout

Item

A row with media, a title, a description, and actions, in three sizes, for lists that are not tables.

Item is a single row built from slots: ItemMedia for an icon or an image, ItemContent with an ItemTitle and an ItemDescription, and ItemActions at the end. ItemGroup stacks rows and ItemSeparator divides them, and ItemHeader and ItemFooter add a full-width line above or below a row. It has three sizes, default, sm, and dense, and Record list builds its rows on dense.

Command palette

Create, assign, jump to a record.

GES-184
Dense row
M
Another dense row

Usage

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item';

<Item size="dense">
  <ItemMedia variant="icon">{/* icon */}</ItemMedia>
  <ItemContent>
    <ItemTitle>Command palette</ItemTitle>
    <ItemDescription>Create, assign, jump.</ItemDescription>
  </ItemContent>
</Item>

Item holds no data, so you map your own records into rows and keep what a row does when it is pressed. Rows inside an ItemGroup are a list.

Three sizes

default is p-4 with a 16px gap and sm is px-4 py-3 with a 10px gap. Both wrap when they run out of room. dense is px-3 py-1.5 with an 8px gap and does not wrap, so it holds one line of text, for a long list of records. ItemDescription clamps to two lines. The variant is the surface: default is transparent, outline adds a border, and muted is a sunken tint.

Size default

A title with a line under it.

GES-184
Size sm

A title with a line under it.

GES-184
Size dense
GES-184

Media

ItemMedia is a bare slot by default. variant="icon" draws a 32px bordered tile on the sunken surface with a 16px icon, and variant="image" draws a 40px rounded image box that crops with object-cover. When the row has a description, the media aligns to the top of the text and not the middle.

asChild renders the row as its one child, so a row can be a link. A row rendered as a link gets the fill background on hover, and any row that can take focus shows a 2px focus ring on :focus-visible.

<Item asChild size="sm">
  <a href="/settings/billing">
    <ItemContent>
      <ItemTitle>Billing</ItemTitle>
    </ItemContent>
  </a>
</Item>

A plain Item is a <div>, which is not focusable, so it has no focus ring and no hover state until you make it interactive. Only a link gets the hover fill, so give a button row its own hover:bg-fill, as Record list does.

Props

Item takes every <div> attribute, plus these. The other parts take the attributes of their element.

PropTypeDefaultWhat it does
variant"default" | "outline" | "muted""default"The surface: transparent, bordered, or a sunken tint.
size"default" | "sm" | "dense""default"The row's padding and gap. dense is one line and does not wrap.
asChildbooleanfalseRenders its one child with the row's styling, for a link or a button.

ItemMedia takes variant, "default" \| "icon" \| "image", and "default" is the default.

Accessibility

  • ItemGroup is a role="list", and each Item in it a role="listitem", so a screen reader announces the count. An Item with asChild keeps its link or button role inside a list item of its own. A row outside a group has no role, and a role you pass replaces either, like role="option" in a listbox, as Record list does.
  • ItemTitle is a <div> and not a heading, and ItemDescription is a <p>.
  • With asChild, the child keeps its own role, so an <a> is a link and a <button> is a button, and Enter and Space work as they do natively. Put the whole row's name in its title, since the link's name is all of its text.
  • A focusable row shows a 2px ring on :focus-visible in the ring color. The row has no 44px touch minimum, so give an interactive row the height you need.
  • Color changes on hover take 100ms and stop under reduced motion.
  • ItemSeparator is decorative by default, so a screen reader skips it.

When to use something else

  • Record list for records grouped into buckets with the keyboard moving between rows and Enter opening one. It is built on Item at dense, so reach for Item when you only need rows and not the grouping or the keys. The Issues example uses it.
  • Table when rows have columns to compare. An Item has slots, not columns.
  • Card for a framed group of content, where an Item is one line of a list. Interactive card when that frame is the thing you open, with media above or beside the text.
  • Menu or Command when the rows are choices that run an action and close.

Install

shadcn add @gesso/item

On this page