⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Page Header

The opening block of a page — what it is, and what to do next

Website / Page Header
Download .md file
Open .md in new tab
On this page
  • Anatomy
  • Basic usage
  • Sizes
  • Background image
  • Alignment
  • Actions
  • Semantic guidance
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product

A page header is the first block on a page. It names the page, says what it is in a line, and offers the next step. It runs full bleed, it carries an optional background image, and there is one per page. The class is required; data-size and data-align do the varying.

Page header or site header? A site header is chrome — the same identity and navigation on every page, unchanged when the route changes. A page header is content — it names this page. The tell is that a site header survives navigation and a page header is replaced by it. Most pages stack both: site header on top, page header under it.

Every generated page on this site already opens with one, which makes the docs site consumer zero. That is why the component behaves in one way the rest of the system does not — see Sizes.


Anatomy

header.page-header               data-size="small|medium|large" · data-align="center"
  div.page-header-media          optional; absolutely positioned behind the content
    img.page-header-image
  div.page-header-container      the gutter, the measure, and the stacking context
    p.eyebrow
    h1.page-header-title
    p.page-header-subtitle
    div.page-header-actions
      a.button
Axis Mechanism Example
Height data-size data-size="large"
Alignment data-align data-align="center"
Background image child element <div class="page-header-media">

The component defines its own tokens, all prefixed --page-header-, and every one of them resolves to a system token. Nothing here reads a primitive.


Basic usage

The bare class is the whole component: a full-bleed band, its own padding, a faded surface and a rule underneath.

Every decision starts here

The tokens, patterns and components that make a brand hold together across every surface it appears on.

<header class="page-header">
  <div class="page-header-container">
    <h1 class="page-header-title">Every decision starts here</h1>
    <p class="page-header-subtitle">The tokens, patterns and components that make a brand hold together.</p>
  </div>
</header>

The demos on this page use <h2> because the page already has an <h1>. In production the title is the page's <h1>. The size comes from , not from the element, so both look the same.


Sizes

data-size gives the header a min-height. Three steps: small, medium, large.

Without data-size a header has no height at all — it is as tall as its padding and its content. That is deliberate, and it is the one place this component breaks the house convention that the bare class means "medium". Every one of the 104 generated headers on this site is content-sized, and a height on the bare class would silently stretch all of them. It is also the honest default: a header with no picture has no reason to reserve space it will not fill. Reach for a size when there is media behind the text, or when a run of pages needs to open at the same height whatever their titles run to.

Each step is clamped — a rem floor, a viewport middle, a rem ceiling. The floor stops a landscape phone collapsing a large header into a strip; the ceiling stops a wide display turning it into a screen of empty ground.

Value Height
(none) Content plus padding
data-size="small" clamp(12rem, 24vh, 18rem)
data-size="medium" clamp(18rem, 40vh, 28rem)
data-size="large" clamp(24rem, 60vh, 40rem)

Small

Settings

<header class="page-header" data-size="small">…</header>

Medium

Reporting

Delivery, viewability and spend across every live campaign.

<header class="page-header" data-size="medium">…</header>

Large

Built to be handed over

A brand system a team can run without asking anyone what the rules are.

<header class="page-header" data-size="large">…</header>

A product that genuinely wants a viewport-height opening re-points the token rather than adding a fourth value:

.page-header[data-size="large"] {
  --page-header-min-height-large: 100svh;
}

Background image

The picture is a child <img>, not a background-image. That is what lets it carry alt text, srcset, loading and fetchpriority, and it is why the picture still appears when a browser or a user declines background images.

Adding also switches the text and the buttons to the light-on-dark treatment, automatically. There is no theme flag to remember and therefore no way for the two to disagree. A button that states its own data-color is left alone — a danger button is a deliberate choice, and its semantic fill carries its own contrast.

Case study

One system, every channel

How a rebuilt token layer cut off-brand revisions to near zero.

Read the study See the system
<header class="page-header" data-size="medium">
  <div class="page-header-media">
    <img class="page-header-image" src="/images/case-study.jpg" alt="" fetchpriority="high">
  </div>
  <div class="page-header-container">
    <p class="eyebrow">Case study</p>
    <h1 class="page-header-title">One system, every channel</h1>
    <p class="page-header-subtitle">How a rebuilt token layer cut off-brand revisions to near zero.</p>
    <div class="page-header-actions">
      <a class="button" href="/case-studies/one-system">Read the study</a>
    </div>
  </div>
</header>

A scrim sits over the picture so the text keeps its contrast whatever the image does. It is a token, so an image that already carries its own contrast can drop it — but check the result against the real photograph, not a placeholder:

.page-header {
  --page-header-scrim: transparent;
  --page-header-color-on-media: var(--text-primary);
}

Alignment

Content aligns to the start of the line by default. data-align="center" centres the container, the text and the actions together.

Iconography

Draw once, use everywhere

Every icon in the set, at every size it ships in.

<header class="page-header" data-size="small" data-align="center">…</header>

Centre a header that is only a title and a line under it. Start-align one that carries actions or a long subtitle — a centred ragged block of prose is harder to read, and centred buttons are harder to find twice.


Actions

is a row that wraps. It holds buttons and nothing else invents its own spacing; the cluster takes a little more separation than the subtitle does, which is what marks it as the next step rather than more of the sentence.

Start a brand instance

Tokens, components and docs, running under your own values.

Get started Talk to us
<div class="page-header-actions">
  <a class="button" href="/start">Get started</a>
  <a class="button" data-variant="outline" href="/contact">Talk to us</a>
</div>

Two actions at most. A third is a sign the page has not decided what it wants the reader to do.


Semantic guidance

Use <header> for the element. It is the header of the page's main content. Nested inside <main> it produces no landmark of its own, which is correct — the page already has one banner, and it is the site header.

The title is the page's <h1>, and there is only one. If a page needs a second heading at that level, it needs a second page.

Actions are <a class="button"> when they navigate and <button class="button"> when they act. A "Get started" that goes to a signup page is a link, whatever it looks like.


Accessibility

  • The title must be the page's <h1>. sets the size, not the outline level — the element still has to be right. A <div class="page-header-title"> looks correct and leaves the page with no heading at all.
  • A decorative background image takes alt="". It is a surface, not content, and a screen reader reading out a stock photograph's description before the page title helps nobody. If the picture genuinely carries meaning the text does not, describe it — but then ask whether it belongs in the page body instead.
  • Check the real photograph for contrast, not the scrim's default. rgba(0, 0, 0, 0.45) clears 4.5:1 against most images and not against all of them. A bright sky behind a subtitle is the case that fails.
  • is not a heading. It is a label above the title, marked up as a <p>. Making it an <h2> above the <h1> inverts the outline.
  • Do not put navigation in a page header. That is the site header's job, and two navigation landmarks at the top of a page is one more than anyone wants to tab through.
  • Never add role="banner". A <header> nested inside <main> — which is where a page header belongs — is deliberately not a landmark. The page's one banner is the site header, and adding a second sends a screen-reader user to the wrong place when they jump to it.
  • Forced-colours mode hides the media and returns the header to Canvas/CanvasText. The scrim does not survive that mode, and light text over a flattened image would be unreadable.

Usage rules

Do Don't
One page header per page, first thing in the content Open a section further down the page with one
Make the title the page's <h1> Use on a <div>
Let a header without media be content-sized Add data-size because every other component has one
Use data-size when there is media behind the text Set a height on
Re-point a min-height token for a viewport-height opening Add a fourth data-size value
Put the picture in as an <img> Set a background-image on
Give a decorative picture alt="" Describe a stock photograph to a screen reader
Keep to two actions Offer a third and let the reader choose
Start-align a header that carries actions Centre a long subtitle
Compose in Hand-place an action with a margin
Let space the content Add margins to the title or subtitle

CSS reference

This section documents how the component is built. For usage, see the sections above.

Tokens

Token Default What it controls
The surface
var(--border-s) solid var(--border-faded) The rule under the header
Title and eyebrow
Subtitle
Block padding; drops to below 768px
Gutter; drops to below 768px
The container's measure
Space between eyebrow, title and subtitle
Space between buttons
clamp(12rem, 24vh, 18rem) data-size="small"
clamp(18rem, 40vh, 28rem) data-size="medium"
clamp(24rem, 60vh, 40rem) data-size="large"
rgba(0, 0, 0, 0.45) The wash over the media; transparent to drop it
Text colour once media is present
Ground behind the picture while it loads
Reading measure for the subtitle
Title size; steps to below 959px

Selectors

Selector Purpose
Base; a centred flex column, full width, padded, on the faded surface
.page-header[data-size="small|medium|large"] min-height from the matching token
.page-header[data-align="center"] Centres the container, the subtitle and the actions
.page-header:has(.page-header-media) Re-points colour and ground for text over a picture, and clears the bottom rule
.page-header:has(.page-header-media) .button:not([data-color]) Re-points so an outline button is not dark type on a dark wash
…:not([data-color]):not([data-variant]) Flips the filled button's label, which the base sets to
position: absolute; inset: 0, behind the content
.page-header-media::after The scrim
object-fit: cover at the full size of the media wrapper
Gutter, measure, z-index: 1, and the gap between content elements
Size and line height; margins zeroed
Muted colour, , reading measure
Wrapping row of buttons
@media (forced-colors: active) Hides the media and returns the header to the system palette

Key rules

Media is a child, not a background. The picture is a real <img> inside an absolutely positioned wrapper, so it keeps alt text and loading hints and survives a browser that drops background images. It also means the scrim rides on the media wrapper — a header with no picture has no scrim to unset.

Nothing clips the header. An object-fit: cover image already fills the wrapper exactly, so no overflow: hidden is needed on the shell — and adding one would clip any Flip or scroll animation that leaves the header, which this site's own page transitions do.

The light-on-dark switch is keyed on :has(.page-header-media), not on an attribute. An author who adds a picture cannot then forget to add a matching theme flag, because there is no flag. It covers the buttons as well as the prose, and it has to: the filled button and the outline button fail in opposite directions over a scrim, so re-pointing one colour would have fixed one and broken the other.

--page-header-color-on-media is fixed light rather than . The scrim is dark in both light and dark themes, so a token that flips with the theme would fail contrast in one of them.

There is no implicit medium. Button, Avatar and Book Cover all treat the bare class as their middle step. This component cannot, because 106 existing headers use the bare class and a height on it would stretch every one. See Sizes.

The docs site is consumer zero, and it now adds nothing. Every page it generates emits the anatomy above from a single builder, and there is no rule left in docs-site.css — not the surface, not the centring, not a mobile height. The header is the component and only the component, which is the state a consumer should be able to reach.

Getting there took deleting two overrides that had stopped matching anything without anyone noticing: one scoped to the pre-component markup, one to a DOM shape that changed underneath it. Worth knowing as a consumer, because it is the failure this component's tokens exist to prevent — if a page needs a different header, re-point a --page-header-* token or ask for a data-size, rather than writing a selector that has to keep agreeing with someone else's markup.


Use in another product

The design system installs once per product:

npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0

This component's styles ship in design-system.css. No JavaScript, nothing else to include.

On this page
  • Anatomy
  • Basic usage
  • Sizes
  • Background image
  • Alignment
  • Actions
  • Semantic guidance
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product
Previous Avatar
Next Section Header

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default