Card headers

The title bar of a card: title, badge, description, avatar, and two trailing slots that differ below desktop. Extracted from the vendored table per ADR-0001.

Demo

One component, ten props. Turn on the avatar for the primary axis, and both trailing slots for the pair the sheet draws.

Team members

10/20 seats

Manage your team members and their account permissions here.

size
contentTrailing
import { CardHeader } from "@/components/application/card-header/card-header"; import { Button } from "@/components/base/buttons/button"; import { Dropdown } from "@/components/base/dropdown/dropdown"; <CardHeader title="Team members" titleElementType="h3" badge="10/20 seats" description="Manage your team members and their account permissions here." actions={<><Button color="secondary" size="sm">Secondary</Button><Button size="sm">Primary</Button></>} contentTrailing={ <Dropdown.Root> <Dropdown.DotsButton aria-label="Team member options" /> <Dropdown.Popover className="w-min"> <Dropdown.Menu> <Dropdown.Item>View profile</Dropdown.Item> </Dropdown.Menu> </Dropdown.Popover> </Dropdown.Root> } />

The card around it is yours

The playground draws its own card so the header has an edge to sit against. CardHeader renders bg-primary, its padding and an optional bottom rule — the border, the radius and the shadow belong to the shell you compose it into, which is what makes the same component work inside a table card, a modal and a plain panel.

Contextual demo

The title bar of something: a data card, a conversation, a content card, a dashboard. The preview shows all four.

The family

What the module exports, and the adapter in the table that now renders it.

The components in this family, with their descriptions and import paths
ComponentDescriptionImport from
CardHeaderThe whole family. One component, ten props, two slots that differ only at Mobile. Everything the sheet draws is here except the card around it.@/​components/​application/​card-header/​card-header
TableCard.HeaderThe adapter. Reads the table's size context, passes it down, turns the divider on. Its public API is unchanged from before the extraction; reach for CardHeader directly anywhere outside a Table.@/​components/​application/​table/​table

Anatomy

A row: optional avatar, a text block taking the remaining width, up to two trailing slots. Below, every part at once.

Olivia Rhye

New user

olivia@untitledui.com

Every part of the family in one header, at the size the sheet draws (md) and with the divider off, which is the default
Part
What it is
What it measures
RootThe header box. Wraps below `md` so the actions can drop to a second row; from `md` up it is one centered row, so the actions and the trailing control share one axis (ruled 2026-08-26).flex-wrap, items-start, 16px gap, md:flex-nowrap md:items-center
PaddingVertical padding is fixed per size; the side inset is the axis that moves at the breakpoint.md: 20 vertical, 16 → 24 sides; sm: 16 vertical, 16 → 20 sides
AvatarThe family's primary axis. Present, it inserts a row wrapper between the root and the text — top-aligned with a description, centered without one.48px, 8px to the text; items-start → items-center when description is absent
Text blockTitle row over description, and the one place the sheet contradicted itself.2px gap, min-w-0, flex-1
Title and badgeA `h2` by default, with the badge beside it.6px gap, items-center
DescriptionWraps. The sheet truncates this at Desktop only; that was normalised away.text-sm, text-tertiary
ActionsThe button row. `basis-full` below `md` is what makes it wrap; from `md` up `basis-auto` puts it back on the row, before the trailing slot.12px gap, order-3, basis-full → md:basis-auto
Content trailingRendered bare — the component adds no classes to it. Its order comes from the root's last-child selector, so a content-sized control stays beside the title at every width.order-2 → md:order-4, set on the root
DividerOff by default. The table turns it on.1px border-secondary, bottom

The avatar axis

The family's primary axis, the one thing TableCard.Header could not express. Two of the four symbols, one wrapper apart.

Team members

10/20 seats

Manage your team members and their account permissions here.

Avatar off: the text block is a direct child of the root, so it carries order-1 itself and no wrapper is rendered at all

Olivia Rhye

New user

olivia@untitledui.com

Avatar on: a row wrapper holds the avatar and the text at gap-2, and it takes the flex-1 the text block had. The sheet draws Avatar size=lg — 48px — with border and no status dot

The two trailing slots

actions and contentTrailing behave identically from md up, differently below — the whole reason there are two slots.

Team members

10/20 seats

Manage your team members and their account permissions here.

From md up: one row that does not wrap. Actions come first, then the trailing slot — which is the sheet's Desktop order and also the source order
Below md, at 375: the actions wrap onto their own row beneath the text at their natural width, and the trailing slot stays beside the title. This cell is the real component inside a real 375px document — an iframe, since the switch is a viewport media query a narrow box cannot trigger
Property
Below md
From md up
Root directionone wrapping rowone row, no wrapping
Row alignmentitems-start — the wrap layoutitems-center — actions and the trailing control share one center axis
Side padding (md)16px24px
Side padding (sm)16px20px
Actionsown row beneath the text, left-aligned at natural widthbeside the text, before the trailing slot
Content trailingbeside the titlebeside the title, last
Vertical padding20px (md) / 16px (sm)unchanged

Sizes

Two, and only md is the sheet's. sm exists because the table's size context needs a smaller header.

Team members

10/20 seats

Manage your team members and their account permissions here.

md — the sheet's only size: 20 above and below, 16 of side inset that opens to 24 from md up

Team members

10/20 seats

Manage your team members and their account permissions here.

sm — kit-extra, for a small table: 16 above and below, and a side inset that opens to 20 rather than 24. Nothing else changes; the type, the gaps and the slots are identical

Composition

The card is yours: a shell owning border, radius and shadow, a CardHeader with divider on, and the body below the rule.

Team members

10/20 seats

Manage your team members and their account permissions here.

Whatever the card is about starts here, under the rule — rows, a chart, a form, a list of members.

The header contributes the rule and nothing else below itself. The body owns its own padding, which is why it can match the header’s inset or deliberately break it, as a full-bleed table does.

TableCard.Root as the shell — though any shell drawing the same chrome works — CardHeader inside it with divider set, and body content below the rule. Exactly one of the two draws the border, and it is not the header

TableCard.Headeris an adapter over this exact component: it reads the table’s size from context, passes it down, and sets divider. The whole story — what moved, the adapter’s unchanged API, and when to reach past it — is the Code tab’s “Extracted out of the table” section.

Built from

The pieces with their own docs page. This family re-authors none of them — every part is a slot or rendered unchanged.

Component
Where it’s used here
AvatarThe primary axis, passed as a slot. The sheet draws size=“lg” with border and no status dot.
BadgeWhat a string badge becomes: gray, modern, sm — the sheet’s badge exactly.
ButtonEvery action, at size sm — the 36px the frame heights resolve to.
DropdownThe overflow menu. Pass aria-label naming the card it acts on.
TableTableCard.Header is an adapter over this component — one code path for both.