Header
The bar that sits on top of something: a back action, a title, whatever belongs on the far end. Three wrapper slots and no assumptions about what goes in them.
API
Props
| prop | type | default | |
|---|---|---|---|
as? | ElementType | "header" | the element or component to render |
className? | string | — | merged over the defaults, so it wins |
…rest | props of as | — | typed against the chosen element |
Header.Leading
Holds on to the start of the bar; usually a back or a menu action.
| prop | type | default | |
|---|---|---|---|
children? | ReactNode | — | whatever belongs on that side of the bar |
className? | string | — | merged over the defaults |
Header.Trailing
Anchors to the end of the bar, whatever comes before it.
| prop | type | default | |
|---|---|---|---|
children? | ReactNode | — | whatever belongs on that side of the bar |
className? | string | — | merged over the defaults |
Header.Title
| prop | type | default | |
|---|---|---|---|
title | ReactNode | — | the text of the bar |
id? | string | — | so another element can reference it, as aria-labelledby does |
className? | string | — | merged over the defaults |
It renders a span, not a heading, because a bar is not always a section: the same Header sits on a dialog, on a sheet and on a mobile screen, and only you know which of those deserves an h2. The id is there so the title can name something else — a modal through aria-labelledby, most of the time.
Examples
Usage
<Header>
<Header.Leading>
<Button aria-label="back" onClickBlur={goBack}>
<Button.Icon icon={<MdArrowBack />} />
</Button>
</Header.Leading>
<Header.Title title="Release notes" />
<Header.Trailing>
<Button aria-label="close" onClickBlur={close}>
<Button.Icon icon={<MdClose />} />
</Button>
</Header.Trailing>
</Header>Mount only what you need
Every slot is optional, and the bar arranges itself around the ones that exist. With no leading slot the title starts at the edge; with no title the trailing slot still anchors to the end.
<Header>
<Header.Title title="Invoices" />
</Header>
<Header>
<Header.Title title="Invoices" />
<Header.Trailing>{actions}</Header.Trailing>
</Header>The slots are wrappers
They take children, not a prop, so what goes in them is entirely yours: two buttons on the trailing side, a chip next to the title, a select instead of a close. And content that belongs to no slot simply sits between them, on the same row.
<Header>
<Header.Title title="Invoice 0142" />
<Chip active>overdue</Chip>
<Header.Trailing>
<Button aria-label="share" onClickBlur={share}>
<Button.Icon icon={<MdShare />} />
</Button>
<Button aria-label="delete" onClickBlur={remove}>
<Button.Icon icon={<MdDelete />} />
</Button>
</Header.Trailing>
</Header>A long title clamps at two lines
The title takes the space the slots leave and clamps at two lines. Two rather than one because a bar often carries a real sentence — a document name, an invoice subject — and cutting it at the first line usually loses the part that identifies it; two rather than unlimited because a header that keeps growing pushes everything below it, and on a bar that repeats across screens the rhythm goes with it.
<Header.Title title={title} className="line-clamp-1" />
<Header.Title title={title} className="truncate" />
<Header.Title title={title} className="line-clamp-none" />
<Header className="items-start">
<Header.Title title={title} />
<Header.Trailing>{close}</Header.Trailing>
</Header>Details
How the anchoring works
The trailing slot carries an automatic left margin, which is what pushes it to the end regardless of what comes before. Two such slots in a row would split the free space between them and drift apart, so a slot that follows another drops that margin — the first one anchors, the rest queue up beside it.
export const headerTrailingClassName = `ml-auto flex shrink-0 items-center gap-2
[[data-margo-header-slot]+&]:ml-0`;It is the same mechanism as the slots of Item, and it is CSS: nothing is measured and the component never looks at its own children.
What the bar renders
<Header as="div" className="border-b-2 border-border">
<Header.Title id={titleId} title="Delete invoice" />
</Header>The root defaults to a header element, which is right for a page or a section. Inside a dialog, where the landmark would be noise, render it as a div.
Changing the clamp
It is a default, not a rule. The class is merged, so a single line, a hard truncation or no clamp at all are one className away — and the same is true of the bar itself: with a title on two lines you may want the actions aligned to the top rather than centred on the block, which is items-start on the Header. The kit picks what is right most of the time and stays out of the way for the rest.