Overview
The Heading component renders typographic section titles (<h1> through <h6>) with consistent styling, semantic correctness, and fluid scaling. Headings are the single most important structural element in any document — they create the outline that sighted users scan visually and screen-reader users navigate programmatically.
Getting headings right is deceptively difficult. The tension between visual hierarchy (how big and bold a heading looks) and semantic hierarchy (its level in the document outline) is the root cause of most heading-related accessibility failures. A Heading component resolves this by decoupling visual appearance from semantic level — you can render an <h3> that looks like an <h1> when the design requires visual prominence but the document outline doesn't.
When to use a Heading:
- Page titles (
<h1>— exactly one per page) - Section and subsection titles (
<h2>through<h6>) - Card titles, dialog titles, sidebar section labels
- Any text that introduces a thematic section of content
When NOT to use a Heading:
- Decorative large text that doesn't introduce a section — use the Text component with a large size variant instead
- Navigation labels — use appropriate nav semantics
- Labels for form fields — use the Label component
- Standalone taglines or slogans — use Text with display styling
Heading font sizes should scale fluidly across viewport widths using CSS clamp(). Design your fluid type scale with the Clamp Calculator, choose your typeface with the Font Explorer, and verify heading-to-background contrast with the Contrast Checker.
Variants
Heading Variants
Semantic Levels
| Level | HTML Element | Typical Use |
|---|---|---|
| h1 | <h1> | Page title. Exactly one per page. |
| h2 | <h2> | Major sections. Most common heading level. |
| h3 | <h3> | Subsections within an h2 block. |
| h4 | <h4> | Sub-subsections. Card titles, sidebar sections. |
| h5 | <h5> | Deep nesting. Rarely needed in well-structured content. |
| h6 | <h6> | Deepest level. Often styled identically to body bold. |
Visual Size Variants (Decoupled from Semantic Level)
| Size | Font Size (Desktop) | Fluid Clamp | Use Case |
|---|---|---|---|
| Display XL | 60–72px | clamp(2.5rem, 5vw + 1rem, 4.5rem) | Hero headlines, landing pages |
| Display | 48–56px | clamp(2rem, 4vw + 0.75rem, 3.5rem) | Marketing sections |
| H1 | 36–40px | clamp(1.75rem, 3vw + 0.5rem, 2.5rem) | Page titles |
| H2 | 28–32px | clamp(1.5rem, 2.5vw + 0.25rem, 2rem) | Section headings |
| H3 | 22–24px | clamp(1.25rem, 2vw + 0.25rem, 1.5rem) | Subsection headings |
| H4 | 18–20px | clamp(1.125rem, 1.5vw + 0.25rem, 1.25rem) | Card titles |
| H5 | 16px | 1rem | Small headings |
| H6 | 14px | 0.875rem | Overlines, eyebrows |
Generate precise clamp() values with the Clamp Calculator. The formula clamp(min, preferred, max) ensures headings scale smoothly from mobile (320px) to desktop (1440px) without media query breakpoints.
Weight Variants
| Weight | Value | Use Case |
|---|---|---|
| Bold | 700 | Default for h1–h3. Strong visual hierarchy. |
| Semibold | 600 | h4–h6. Slightly softer than bold. |
| Medium | 500 | Subtle headings, overlines. |
| Regular | 400 | Display sizes where the size alone provides hierarchy. |
Additional Variants
- With Overline: Small uppercase label above the heading ("CHAPTER 3" → "The Journey Begins")
- With Underline/Accent: Decorative bottom border or brand-colored accent line
- Gradient: CSS gradient text (
background-clip: text) for marketing/hero contexts - Truncated: Single-line truncation with
text-overflow: ellipsisfor constrained layouts
Properties
Heading Properties
| Property | Type | Default | Description |
|---|---|---|---|
level | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Semantic heading level. Determines the HTML element (<h1>–<h6>). |
size | 'display-xl' | 'display' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | Matches level | Visual size variant. Allows decoupling from semantic level. |
weight | 'regular' | 'medium' | 'semibold' | 'bold' | 'bold' | Font weight. |
color | string | 'inherit' | Text color. Accepts design tokens or CSS values. |
align | 'left' | 'center' | 'right' | 'left' | Text alignment. |
truncate | boolean | false | Truncates to a single line with ellipsis. |
maxLines | number | — | Clamps text to N lines with -webkit-line-clamp. |
as | ElementType | Determined by level | Override the rendered element (e.g., render an <h3> semantically but as a <span> visually). |
id | string | — | Element ID. Critical for anchor links and aria-labelledby references. |
className | string | — | Additional CSS classes. |
The level vs size distinction is the key design decision. Setting level={3} renders an <h3> element; setting size="h1" makes it look like an h1. This decoupling is essential when a component (e.g., a Card title) always uses <h3> semantically but may appear in different visual contexts.
Token Mappings
Design Token Mappings
| Token | Role | Typical Value |
|---|---|---|
--heading-font-family | Heading typeface | var(--font-family-display) or var(--font-family-sans) |
--heading-font-weight | Default weight | 700 |
--heading-color | Default heading color | var(--color-text-primary) |
--heading-line-height | Line height | 1.2 (tight for large sizes) to 1.4 (for h5–h6) |
--heading-letter-spacing | Tracking | -0.02em (large sizes) to 0 (small sizes) |
--heading-margin-top | Space above heading | 0 (controlled by parent layout) |
--heading-margin-bottom | Space below heading | var(--space-3, 0.75rem) |
--heading-h1-size | H1 font size | clamp(1.75rem, 3vw + 0.5rem, 2.5rem) |
--heading-h2-size | H2 font size | clamp(1.5rem, 2.5vw + 0.25rem, 2rem) |
--heading-h3-size | H3 font size | clamp(1.25rem, 2vw + 0.25rem, 1.5rem) |
--heading-h4-size | H4 font size | clamp(1.125rem, 1.5vw + 0.25rem, 1.25rem) |
--heading-h5-size | H5 font size | 1rem |
--heading-h6-size | H6 font size | 0.875rem |
--heading-display-xl-size | Display XL | clamp(2.5rem, 5vw + 1rem, 4.5rem) |
--heading-display-size | Display | clamp(2rem, 4vw + 0.75rem, 3.5rem) |
Choose your heading typeface with the Font Explorer. For fluid sizes, generate clamp() values with the Clamp Calculator. A common pattern: use a display/serif font for h1 and a sans-serif for h2–h6, or maintain a single family and rely on size/weight for hierarchy.
States
Heading States
Headings are primarily static content elements, so their "states" are contextual rather than interactive:
| State | Visual Treatment | Context |
|---|---|---|
| Default | Rendered at specified size, weight, and color. | Normal content display. |
| Anchor Link Target | Scroll target with optional highlight animation (flash background or underline). | When a user navigates to #section-id, the heading briefly highlights. Use scroll-margin-top to offset fixed headers. |
| Editable | Cursor changes to text; heading becomes an inline editable field. | CMS interfaces, title editing in productivity apps. |
| Skeleton / Loading | Replaced by a skeleton placeholder (rectangle at heading width/height). | Content loading state. See Skeleton. |
| Truncated | Text cut off with ellipsis. Full text accessible via title attribute or Tooltip. | Constrained layouts (card titles, sidebar headings). |
| Error / Invalid | Red color or error icon (rare). | Form section headings in validation error state. |
Scroll-Margin for Fixed Headers
When using anchor links with fixed/sticky navigation bars, headings need scroll-margin-top to prevent them from hiding behind the fixed element:
[id] {
scroll-margin-top: calc(var(--navbar-height, 64px) + 1rem);
}
This ensures that clicking a table-of-contents link scrolls the heading into view below the fixed nav, not behind it.
Accessibility
Accessibility
Headings are the primary mechanism screen-reader users rely on for page navigation. A well-structured heading hierarchy is arguably the single highest-impact accessibility improvement you can make.
Semantic Hierarchy (WCAG 1.3.1 Info and Relationships):
- Every page must have exactly one
<h1>, representing the page's primary topic. - Headings must follow a logical descending order:
<h1>→<h2>→<h3>. Never skip levels (e.g.,<h2>→<h4>). - The heading hierarchy creates an implicit document outline. Screen-reader users navigate this outline directly — JAWS, NVDA, and VoiceOver all support "next heading" (H key) and "heading level N" (1–6 keys) shortcuts.
- When visual design requires a heading to look different from its semantic level, use the
sizeprop to decouple appearance from semantics. A<Heading level={3} size="h1">renders an<h3>that looks like an h1.
Heading-Level Audit: Run a heading-level audit on every page. Tools: axe DevTools, WAVE, or the HeadingsMap browser extension. Common violations:
- Multiple
<h1>elements (often: site logo in<h1>plus page title in<h1>) - Skipped levels (often:
<h1>then<h3>because h2 "looked too big") - Headings used for visual styling, not structure (a bold line that isn't a heading)
- Non-heading text used where a heading is needed (a
<div class="title">instead of<h2>)
Color Contrast (WCAG 1.4.3 Contrast Minimum):
- Heading text must achieve at least 4.5:1 contrast against its background for text under 24px, and 3:1 for text at or above 24px (18.66px bold). Since headings are typically large and bold, most qualify for the 3:1 large-text threshold — but verify with the Contrast Checker.
- Gradient text (
background-clip: text) must maintain contrast across the entire gradient range, not just at one point.
Landmark Labeling: Headings frequently serve as labels for landmarks. For example:
<section aria-labelledby="features-heading">
<h2 id="features-heading">Features</h2>
…
</section>
This connects the heading to its section landmark, allowing screen readers to announce "Features, region" when navigating by landmark (WCAG 1.3.1).
Reading Order: Ensure headings appear in the DOM in the correct reading order, not just visually via CSS positioning. Screen readers follow DOM order, not visual order (WCAG 1.3.2 Meaningful Sequence).
Usage Guidelines
Usage Guidelines
Do:
- Use exactly one
<h1>per page, matching the page's primary topic - Follow a strict descending hierarchy: h1 → h2 → h3 (no skipping)
- Decouple visual size from semantic level when needed (
level={3} size="h1") - Use fluid typography with
clamp()for responsive scaling — configure via the Clamp Calculator - Add
idattributes to headings that serve as anchor link targets - Set
scroll-margin-topto offset fixed headers when using anchor links - Choose heading fonts that complement body text — explore with the Font Explorer
- Use tighter
line-height(1.1–1.2) for large headings and looser (1.3–1.4) for small headings - Apply negative
letter-spacing(-0.01em to -0.03em) for large display sizes
Don't:
- Don't use headings purely for visual styling — if it's not a section title, use Text
- Don't skip heading levels for visual reasons — use the
sizeprop instead - Don't put interactive elements inside headings (buttons, links) — place them adjacent
- Don't use more than 3–4 heading levels on a typical page — deep nesting signals IA problems
- Don't rely solely on font size to distinguish headings from body text — combine size, weight, and spacing
- Don't set heading margins to zero without providing spacing via parent layout — headings need vertical breathing room
Typography Pairing: A common pattern is pairing a display/serif font for h1/h2 with a sans-serif for h3–h6. This creates visual distinction without relying solely on size. The Font Explorer helps you preview pairings. Ensure both fonts share similar x-heights for visual harmony.
Code Snippets
<!-- Heading – Semantic HTML with Fluid Typography -->
<h1 class="heading heading-h1">Page Title</h1>
<h2 class="heading heading-h2">Section Title</h2>
<h3 class="heading heading-h3">Subsection Title</h3>
<!-- Heading with anchor link target -->
<h2 class="heading heading-h2" id="features">Features</h2>
<!-- Display variant on a semantic h1 -->
<h1 class="heading heading-display-xl">Launch Day</h1>
<!-- Heading with overline -->
<div class="heading-group">
<span class="heading-overline">Chapter 3</span>
<h2 class="heading heading-h2">The Journey Begins</h2>
</div>
<style>
.heading {
font-family: var(--heading-font-family, system-ui, sans-serif);
font-weight: var(--heading-font-weight, 700);
color: var(--heading-color, #111827);
margin-top: 0;
margin-bottom: var(--heading-margin-bottom, 0.75rem);
}
.heading-display-xl {
font-size: clamp(2.5rem, 5vw + 1rem, 4.5rem);
line-height: 1.1;
letter-spacing: -0.03em;
}
.heading-display {
font-size: clamp(2rem, 4vw + 0.75rem, 3.5rem);
line-height: 1.1;
letter-spacing: -0.02em;
}
.heading-h1 {
font-size: var(--heading-h1-size, clamp(1.75rem, 3vw + 0.5rem, 2.5rem));
line-height: 1.2;
letter-spacing: -0.02em;
}
.heading-h2 {
font-size: var(--heading-h2-size, clamp(1.5rem, 2.5vw + 0.25rem, 2rem));
line-height: 1.25;
letter-spacing: -0.01em;
}
.heading-h3 {
font-size: var(--heading-h3-size, clamp(1.25rem, 2vw + 0.25rem, 1.5rem));
line-height: 1.3;
}
.heading-h4 {
font-size: var(--heading-h4-size, clamp(1.125rem, 1.5vw + 0.25rem, 1.25rem));
line-height: 1.35;
}
.heading-h5 { font-size: var(--heading-h5-size, 1rem); line-height: 1.4; }
.heading-h6 { font-size: var(--heading-h6-size, 0.875rem); line-height: 1.4; text-transform: uppercase; letter-spacing: 0.05em; }
.heading-overline {
display: block;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-brand, #2563eb);
margin-bottom: 0.25rem;
}
/* Anchor link scroll offset */
[id] {
scroll-margin-top: calc(var(--navbar-height, 64px) + 1rem);
}
/* Truncation */
.heading-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>interface HeadingProps {
level?: 1 | 2 | 3 | 4 | 5 | 6;
size?: "display-xl" | "display" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
weight?: "regular" | "medium" | "semibold" | "bold";
align?: "left" | "center" | "right";
color?: string;
truncate?: boolean;
maxLines?: number;
id?: string;
className?: string;
children: React.ReactNode;
}
function Heading({
level = 2,
size,
weight = "bold",
align = "left",
color,
truncate = false,
maxLines,
id,
className,
children,
}: HeadingProps) {
const Tag = `h${level}` as keyof JSX.IntrinsicElements;
const visualSize = size || `h${level}`;
const weightMap = { regular: 400, medium: 500, semibold: 600, bold: 700 };
const style: React.CSSProperties = {
fontFamily: "var(--heading-font-family, system-ui, sans-serif)",
fontWeight: weightMap[weight],
color: color || "var(--heading-color, #111827)",
textAlign: align,
marginTop: 0,
marginBottom: "var(--heading-margin-bottom, 0.75rem)",
...(truncate && {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap" as const,
}),
...(maxLines && {
display: "-webkit-box",
WebkitLineClamp: maxLines,
WebkitBoxOrient: "vertical" as const,
overflow: "hidden",
}),
};
return (
<Tag id={id} className={`heading heading-${visualSize} ${className || ""}`} style={style}>
{children}
</Tag>
);
}
// Usage: Semantic h3 styled as h1
<Heading level={3} size="h1">
Card Title That Looks Big
</Heading>
// Usage: Display heading for hero
<Heading level={1} size="display-xl" weight="bold" align="center">
Design Without Limits
</Heading>
// Usage: Truncated heading
<Heading level={4} truncate>
This very long heading will be cut off with an ellipsis when it overflows its container
</Heading>
// Usage: Heading with overline
<div>
<span className="heading-overline">Case Study</span>
<Heading level={2} size="h1">Redesigning the Dashboard</Heading>
</div>Design Systems
Design System Implementations
Material Design (MUI) uses a <Typography> component with variant prop for both headings and body text. Heading variants: h1 through h6, plus subtitle1, subtitle2. The component prop decouples the rendered element from the visual variant — <Typography variant="h1" component="h3"> renders an <h3> with h1 styling. MUI's default type scale uses Roboto with sizes from 6rem (h1) down to 1.25rem (h6), with decreasing letter-spacing at larger sizes.
Ant Design provides heading via <Typography.Title> with a level prop (1–5). Sizes are predefined and follow Ant's 14px base scale. The component supports copyable, editable, ellipsis (with configurable rows and expandable toggle), and type for semantic coloring (secondary, success, warning, danger). No built-in decoupling of visual size from semantic level.
Chakra UI offers a <Heading> component with as prop for semantic level and size prop (4xl, 3xl, 2xl, xl, lg, md, sm, xs) for visual size. This cleanly decouples semantics from appearance. Chakra's heading inherits from Box, supporting all style props. Sizes use responsive array syntax: size={["lg", "xl", "2xl"]} for viewport-based scaling.
Bootstrap styles headings via .h1–.h6 classes (applicable to any element) and .display-1–.display-6 for larger display headings. This CSS-class approach inherently decouples visual style from semantics. Bootstrap 5 uses $font-size-base * multiplier for heading sizes, with responsive scaling via $enable-rfs (Responsive Font Sizes) which applies calc() scaling similar to clamp().
Apple Human Interface Guidelines specifies Dynamic Type styles: Large Title, Title 1–3, Headline, Subheadline. These map to semantic roles rather than HTML levels. In SwiftUI, .font(.largeTitle) etc. iOS heading styles automatically respond to the user's preferred text size (accessibility setting), scaling from the default 34pt (Large Title) to significantly larger or smaller based on the Dynamic Type slider.
Tailwind CSS provides heading sizing through utility classes: text-4xl, text-3xl, etc. Combined with font-bold, tracking-tight, and leading-tight, developers compose heading styles directly. Tailwind doesn't provide a heading component — it's utility-first, leaving component abstraction to the developer or to component libraries like Shadcn/ui that use <h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">.