Atom · stable · v1.0.0 · owner: Site system lead · contract 19 of 20 points (open: figma)
Do and don't
Do: A compact section title
Rendered by heading.tsOpen example: Heading · Do: A compact section title Use the full-page example to follow page links.
Do: A section title
Rendered by heading.tsOpen example: Heading · Do: A section title Use the full-page example to follow page links.
Do: A card title
Rendered by heading.tsOpen example: Heading · Do: A card title Use the full-page example to follow page links.
Don't: Capitals as a label
Rendered by heading.tsOpen example: Heading · Don't: Capitals as a label Use the full-page example to follow page links.
Don't: A display size on a section
Rendered by heading.tsOpen example: Heading · Don't: A display size on a section Use the full-page example to follow page links.
Rules
| Rule | Why | Check |
|---|---|---|
| Exactly one h1 per page. | Screen-reader users jump to the h1 to learn what the page is about. | Automated: scripts/site/system/compose.ts (outline check) |
| Heading levels never skip. | A skipped level suggests missing content. | Automated: scripts/site/system/compose.ts (outline check) |
| A heading never breaks inside a word or overflows its box. | Broken words ('engagem/ent') look like a mistake and lose the word (owner, 26 Oct). | Automated: tests/site/headings.spec.ts (every page, 1440 and 390) |
| Section headings use the h2 size, never display. | Display at 96 px does not fit a half-page column without breaking words. | Automated: tests/site/headings.spec.ts (h2 at most 56 px) |
API
| Prop | Type | Required | Default | What it does |
|---|---|---|---|---|
level | 1 | 2 | 3 | Yes | The outline level. | |
text | string | Yes | The heading text. | |
id | string | No | The anchor target and the section's accessible name. | |
size | display | h2 | h3 | No | The visual size when it differs from the level (the hero h1 is display). | |
html | boolean | No | false | The text already contains inline markup (Japanese <wbr>). |
extraClass | string | No | A class owned by the parent element. |
Contract details
The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests
Identity
| Aspect | Value |
|---|---|
| Id | heading |
| Layer | Atoms |
| Category | typography |
| Owner | Site system lead |
| Status | stable |
| Version | 1.0.0 |
| Source | apps/showcase/site/system/atoms/heading.ts (private repository; the contract and the render function are this one module) |
| Version | Date | Change |
|---|---|---|
| 1.0.0 | 2026-10-26 | Four sizes (display, h2, h3, Japanese display); headings never break inside a word. |
Use when
- A page title (one h1) or a section title (h2). Why: Headings are how people and screen readers move through a page.
- A card, tile or step title (h3). Why: A smaller heading keeps the outline correct inside a section.
Do not use when
- To make text big for emphasis. Why: Size without structure breaks the outline screen readers navigate by.
- Skipping a level (h2 to h4). Why: Skipped levels make people think a section is missing.
Anatomy
| Part | What it is |
|---|---|
| root | h1, h2 or h3 with the type role of its size. |
Made of: tokens:type.
Used in: figure-frame, atomic-guide, contact-form, contents, faq-list, jobs-strip, library-grid, panel, showcase-frame, status-table, cta-section, flow-section, founder-section, hero, job-section, lead-section, page-head, problem-section, proof-section, related, roadmap-strip, roles-section, titled-section.
Variants
| Variant | When | Why |
|---|---|---|
| display | The page's h1 only. | One very large line sets the page's subject; more than one would compete. |
| h2 | Section titles, capped at 56 px. | Large enough to carry a section, small enough that the longest word fits a column. |
| h3 | Cards, tiles and steps, 20 to 24 px. | Three-column grids need titles that fit a third of the page without breaking words. |
States
| State | Rendered or not applicable |
|---|---|
| default | default |
| hover | Not applicable: A heading is not interactive, so it has no hover state. |
| focus-visible | Not applicable: Headings receive focus only from the skip link and in-page anchors, where the outline is suppressed (main:focus, h1:focus). |
| active | Not applicable: A heading cannot be pressed. |
| disabled | Not applicable: A heading has nothing to disable. |
| loading | Not applicable: A heading is rendered at build time and is complete when the page loads. |
| empty | Not applicable: A heading is not rendered without content; the builder skips it. |
| error | Not applicable: A heading shows no data that can fail. |
| current | Not applicable: A heading has no current item. |
| visited | Not applicable: A heading is not a link. |
| expanded | Not applicable: A heading does not open or close. |
Tokens
| Part | Token |
|---|---|
| display.font | --type-display |
| h2.font | --type-h2 (30 to 56 px) |
| h3.font | --type-h3 (20 to 24 px) |
| root.letter-spacing | --ls-display / --ls-h2 / --ls-h3 |
| ja.font | --font-display-ja, letter-spacing 0, normal width |
| --heading-font-type-h1 | var(--type-h1) |
| --heading-letter-spacing-ls-h1 | var(--ls-h1) |
| --heading-font-type-h2 | var(--type-h2) |
| --heading-letter-spacing-ls-h2 | var(--ls-h2) |
| --heading-font-type-h3 | var(--type-h3) |
| --heading-letter-spacing-ls-h3 | var(--ls-h3) |
| --heading-font-weight-weight-heavy | var(--weight-heavy) |
| --heading-font-size-fs-h2 | var(--fs-h2) |
| --heading-font-size-fs-h3 | var(--fs-h3) |
Layout
| Aspect | Value |
|---|---|
| max | 22ch for h2 in prose; none for the display |
| breakpoints | Required review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; sizes are fluid between their minimum and maximum. |
| wrapping | Balanced lines, no hyphens, no break inside a word (overflow-wrap normal); Japanese breaks between phrases with line-break strict. |
| textExpansion | Checked at 390 and 1440 by tests/site/headings.spec.ts on every page; Japanese steps down to h3 under 360 px. |
Spacing and rhythm
| Aspect | Value |
|---|---|
| internal | Line height 0.9 (display), 1 (h2), 1.2 (h3). |
| external | --space-7 after an h2 in prose; more space above a heading than below. |
| rhythm | Opens every section (D-SITE-005). |
Motion
| Aspect | Value |
|---|---|
| None | Heading has no intrinsic animation. On generated Site pages, the document shell may apply the registered once-only claude-entrance profile to eligible below-fold section headings (20px, --dur-weave/--ease-enter with capped --stagger). Standalone Heading output remains static; content is never hidden, and reduced motion removes that transform through the shell's .motion-ok policy. |
Runtime and reuse
Render-only element; mount any registered child enhancements separately. Preserve required Site CSS, fonts, responsive and locale rules from nativeElement.
Accessibility passport
| Aspect | Value |
|---|---|
| role | heading, with its level |
| name | Its text |
| keyboard | Not interactive. |
| focus | Only via skip link or anchor. |
| screenReader | Announced with its level; one h1 per page. |
| contrast | Ink on cotton 15.9:1; cotton on every dye 7:1 or better; dark on saffron 9.1:1. |
| target | Not a target. |
Content
| Slot | Length | Voice |
|---|---|---|
| text | Display 6 words, h2 12 words, h3 8 words | A claim or a plain name, sentence case, no full stop in Japanese card headings |
Good: “The next level of your design system.” · “Every week, a design system drifts a little.”
Bad: “Introducing our innovative solution” · “OVERVIEW”
Japanese: Text comes from the page content; Japanese strings are reviewed per page (apps/showcase/site/loom/locales). Awaiting native review, as the banner on every Japanese page says.
Icons and illustrations
None: this element uses no icon or illustration of its own.
Decisions and exceptions
Usage
298 uses across the built pages (measured by npm run site:system).
Tests
| Aspect | Value |
|---|---|
| unit | tests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns |
| visual | Not yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts |
| a11y | tests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark |
| parity | scripts/site/system/check.ts: Library examples are rendered by the same render function the site imports |
Figma
not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.