Heading
A section title that establishes document structure for readers and assistive technology.
Basic usage
Install the package:
pnpm add @design-schema/react @design-schema/tokens Import the stylesheet once for the app, then the component:
import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { Heading } from '@design-schema/react'; Examples
Default
- Default
Account settings
33px / 2.0625rem · 39px / 2.4375rem · font.size.3xl
Not rendered here: a grid shows every tile at once, and this example’s heading would skip a level after the one above it. Its source stands in — the level it names is the point, and the page outline cannot demonstrate it and stay unbroken at the same time.
Subsection Sized Up23px / 1.4375rem · 25px / 1.5625rem · font.size.xl
level
Not rendered here: this example renders a level-1 heading, and a page has one of those — the title at the top of this one. Its source stands in, and it says the same thing either way: level 1 really does produce an <h1>, which is the whole reason this page cannot embed it.
level="1"40px / 2.5rem · 49px / 3.0625rem · font.size.4xl
- level="2"
Account settings
33px / 2.0625rem · 39px / 2.4375rem · font.size.3xl
- level="3"
Account settings
28px / 1.75rem · 31px / 1.9375rem · font.size.2xl
- level="4"
Account settings
23px / 1.4375rem · 25px / 1.5625rem · font.size.xl
- level="5"
Account settings
19px / 1.1875rem · 20px / 1.25rem · font.size.lg
- level="6"
Account settings
16px / 1rem · font.size.md
Not rendered here: this example renders a level-1 heading, and a page has one of those — the title at the top of this one. Its source stands in, and it says the same thing either way: level 1 really does produce an <h1>, which is the whole reason this page cannot embed it.
level="1"40px / 2.5rem · 49px / 3.0625rem · font.size.4xl
- level="2"
Billing
33px / 2.0625rem · 39px / 2.4375rem · font.size.3xl
size
- size="4xl"
Account settings
40px / 2.5rem · 49px / 3.0625rem · font.size.4xl
- size="3xl"
Account settings
33px / 2.0625rem · 39px / 2.4375rem · font.size.3xl
- size="2xl"
Account settings
28px / 1.75rem · 31px / 1.9375rem · font.size.2xl
- size="xl"
Account settings
23px / 1.4375rem · 25px / 1.5625rem · font.size.xl
- size="lg"
Account settings
19px / 1.1875rem · 20px / 1.25rem · font.size.lg
- size="md"
Account settings
16px / 1rem · font.size.md
align
- align="start"
Account settings
- align="center"
Account settings
- align="end"
Account settings
Props
4 rows| Prop | Type | Default | Description |
|---|---|---|---|
| level required | 1 | 2 | 3 | 4 | 5 | 6 | — | Position in the document outline. Controls the semantic element, not the visual size. Canonical values are strings; generated components also accept the number — the exported `HeadingLevel` type is the string union, and the prop or property type adds `1 | 2 | 3 | 4 | 5 | 6`. A missing, out-of-range or non-numeric level (untyped JavaScript, `level="7"`) is treated as `2` on every platform: an <h2> on web and Lit, the 3xl default size everywhere, and one development warning per element for its lifetime. The warning is developer-facing, not copy, so it has no copy key; it names the received value and the level-2 fallback (`Heading: level 7 is not one of 1–6; rendering as level 2.`), printing the value as `String(level)`, so a missing level reads `level undefined`. The warning is issued once per element whatever later values arrive (a later, different invalid value does not warn again), and on web from an effect behind a ref guard rather than during render, so StrictMode cannot send it twice. Behavior scenarios take only canonical values, so each platform's own test file covers a numeric level and the fallback with its single warning; neither gets a story. On Lit the property holds `undefined` until set, and the fallback renders. The Default story renders level `2` with "Account settings" and no other args. Accessibility: Screen-reader users navigate by heading level; levels must not skip (h1 → h3). |
| size | 4xl | 3xl | 2xl | xl | lg | md | — | Visual size, independent of level. There is no single default; the default is read from `level` by this exact map — 1 → 4xl, 2 → 3xl, 3 → 2xl, 4 → xl, 5 → lg, 6 → md — and an explicit `size` always wins over it. On Lit the resolved default is never written back to the `size` attribute, so `[size]` selectors match only explicit sizes; web exposes no size attribute at all, only the `ds-heading--size-*` modifier class, which carries the resolved size. Heading takes the large end of the shared size vocabulary; the exported type is its own, since Text takes the small end. Enum stories keep the literal `<Prop><Value>` casing: `Size4xl`, `Size2xl`, `Level1`. |
| children required | content | — | The heading text. Keep it short and descriptive; it is what appears in the page outline. |
| align | start | center | end | start | Horizontal text alignment. It has no style binding on purpose — alignment is a layout choice, not a themed value — so it maps straight to the platform's text-align. `start` and `end` are logical on web and Lit; React Native has no logical values and resolves them through I18nManager.isRTL at render, so a writing-direction change mid-session does not re-align an already-rendered heading, the same limit Text has. The value set is Text's, so every platform reuses Text's exported `TextAlign` type and exports no separate HeadingAlign, with no deprecated alias; Text exports no mapping helper on web or Lit, so Heading keeps its own `ds-heading--align-*` classes there and its own `:host([align])` selectors on Lit. On web every value, `start` included, emits its `ds-heading--align-<value>` class. |
Accessibility
Decisions this component leaves to you
Choose the heading level so it follows the heading above it on your page. The component will not guess the outline it lands in.
Required props that carry accessibility meaning: level. Each is described in the props table above.
Implicit ARIA role: heading.
Anatomy
What generation guarantees
Heading hierarchy — Headings this component renders take their level from the caller, so the page outline never skips a level.
Contrast — Text contrast meets WCAG AAA; the pairs are listed below.
This component does not animate, so it has no reduced-motion behaviour to describe.