Skip to main content

Strata

A page of titled sections, each one explained beside its content. Every section pairs a header — its title and description — with the controls that header describes. Strata.Root stacks the sections with a hairline between them. It is the shape of a settings page, a resource configuration editor, and any form long enough that each group needs its own sentence.

Below the md breakpoint the header stacks above its own content, so a narrow viewport keeps the pairing rather than squeezing two columns into one. The component owns structure only: the grid, the vertical rhythm, and the rules between sections. Form state, validation, and the controls themselves arrive as composed JSX.

Reach for Card when a group needs a raised surface of its own, for Media Object when the thing beside the text is an image or an icon, and for Description List when the pairs are read-only term and value data rather than headed sections.

Each demo below runs in its own framed document, so its media queries track the frame instead of your browser window. Switch the frame to tablet — Tailwind's md boundary, where the split is at its narrowest — and then to mobile, where every header stacks above its own controls.

Composition

Strata.Root└── Strata.Section    ├── Strata.Header    │   ├── Strata.Title    │   └── Strata.Description    └── Strata.Body
  • Root is the stack. It rules a hairline between adjacent Section children — never above the first or below the last — and it is the place to set --strata-columns for every section at once.
  • Section is one titled band. It owns its own vertical rhythm (py-8 first:pt-0) rather than taking it from Root, so a single section reads correctly on a page with nothing else on it. Below md it stacks Header above Body; at md and up it splits them one to two.
  • Header is the introductory column — Title and Description, spaced by gap-2. It renders a semantic <header>, which groups the introductory content of its own <section>; see Accessibility for why that is not a second banner landmark.
  • Title renders an <h2>, one level below the page's <h1>. Compose asChild when a page nests its sections deeper.
  • Description is the sentence that says what the section's controls do. Optional — a section whose title says everything may omit it.
  • Body is the content column: the controls, cards, or table the header describes. Its gap-4 spaces stacked children, and a consumer's own gap-* replaces it.

Polymorphism

Every part accepts asChild, so the structure composes onto the element a page actually needs. Root renders as the <form> below, which keeps the form element outside every section instead of repeating one per band. Title renders as an <h3> because this section sits under a sub-heading rather than directly under the page <h1>.

Column split

At md and up, Section reads its grid-template-columns from --strata-columns, defaulting to minmax(0, 1fr) minmax(0, 2fr): a one-to-two split whose tracks shrink below their content, so a long single-line value in Body truncates or wraps instead of widening the page. Set the variable on Root to re-split every section at once, or on a single Section to move only that one — custom properties inherit, so the nearest declaration wins. Keep the minmax(0, …) form in an override: a bare 1fr track grows to its content. Below md the variable goes quiet, because the two parts stack in one minmax(0, 1fr) track.

The default suits a short label beside a wide set of controls. When the explanation carries as much weight as the controls, an even split reads better — switch the frame to mobile to watch both splits collapse to the same stack:

Accessibility

  • Sections carry no landmark, by design. Section renders a <section> with no accessible name, so it stays generic in the accessibility tree. Its Title heading is the navigation affordance instead. A heading outline can run long and nest deeply; a landmark list only stays useful while it is short enough that main and nav are one keypress away. A named <section> is a region landmark, so one per section turns that list into a second table of contents the headings already provide.
  • A landmark does not disambiguate controls. When two sections hold controls that read alike — two radio groups both offering "Allow all" — reach for Field.Set and Field.Legend, which name the group on every control inside it. Assistive technology announces a landmark once, on entry, so naming the Section fixes nothing here.
  • Name a Section only when it is a destination. Pass aria-labelledby pointing at the Title's id when a returning user jumps straight to that section and its container beats its heading as the landing spot — a live log, a status readout, or a chart whose heading is separated from its content by a toolbar. One or two such sections on a page is already a lot. Past that, headings do the job better.
  • Header is not a second banner. Header renders a <header>, which maps to the banner landmark only when its nearest sectioning ancestor is the document body. Nested inside Section's <section>, it is generic, so a page carries as many headers as it has sections.
  • Heading level is yours. Title defaults to <h2> for a section directly under a page <h1>. When the sections sit deeper, compose asChild with the correct level rather than letting the outline skip — mantle cannot see the surrounding document.
  • Controls keep their own labels. The Title is a heading, not a label. A group of inputs inside Body still needs its own Field legend or label; a heading above a group names the section, not the control.

API Reference

Strata.Root

The stack of sections. Rules a hairline between adjacent Strata.Section children — never above the first or below the last — and scopes --strata-columns for all of them.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Root styling onto alternative element types or your own components.
Data AttributeValueDescription
data-slot"strata"Stable styling hook. Survives className overrides and asChild.

Every part joins an incoming data-slot chain ahead of its own name, so composing a part through an ancestor's asChild yields data-slot="strata strata-section" rather than losing one of the two.

Strata.Section

One titled band of the page. Stacks Strata.Header above Strata.Body below the md breakpoint, then splits the two into a grid at md and up. Carries its own vertical rhythm (py-8 first:pt-0).

All props from section, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Section styling onto alternative element types or your own components.
CSS VariableDefaultDescription
--strata-columnsminmax(0, 1fr) minmax(0, 2fr)The grid-template-columns value at md and up. Set it on Strata.Root to re-split every section, or here for this section alone. A bare 1fr track keeps the auto minimum and grows to its content.
Data AttributeValueDescription
data-slot"strata-section"Stable styling hook. Survives className overrides and asChild.

Strata.Header

A section's introductory column — its Strata.Title and Strata.Description, spaced by gap-2. Renders a semantic <header>, which is generic rather than a banner landmark inside Strata.Section.

All props from header, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Header styling onto alternative element types or your own components.
Data AttributeValueDescription
data-slot"strata-header"Stable styling hook. Survives className overrides and asChild.

Strata.Title

The section's heading. Renders an <h2>, one level below the page's <h1>. Compose asChild with the right element when a page nests its sections deeper.

All props from h2, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Title styling onto alternative element types or your own components.
Data AttributeValueDescription
data-slot"strata-title"Stable styling hook. Survives className overrides and asChild.

Strata.Description

The sentence that says what the section's controls do. Renders a <p> in muted body text beneath Strata.Title. Optional.

All props from p, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Description styling onto alternative element types or your own components.
Data AttributeValueDescription
data-slot"strata-description"Stable styling hook. Survives className overrides and asChild.

Strata.Body

The section's content column — the controls, cards, or table Strata.Header describes. Its gap-4 spaces stacked children, and a consumer's own gap-* replaces it.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the Strata.Body styling onto alternative element types or your own components.
Data AttributeValueDescription
data-slot"strata-body"Stable styling hook. Survives className overrides and asChild.