# Heading

> Section and page headings. Level and visual size decoupled so you can honour document outline without sacrificing composition.

- Category: primitive
- Status: stable (since 0.3.0)
- A11y pattern: https://www.w3.org/WAI/ARIA/apg/patterns/
- Tokens: --font-sans, --foreground-primary
- Playground: https://design.freecodecamp.org/playground#heading
- npm dependencies: `react@>=18 <20`
- Registry dependencies: [theme](https://design.freecodecamp.org/registry/theme.md)
- Files:
  - `Heading.tsx` → `src/ui/heading/Heading.tsx` (raw: https://design.freecodecamp.org/registry/heading/Heading.tsx)
  - `heading.css` → `src/ui/heading/heading.css` (raw: https://design.freecodecamp.org/registry/heading/heading.css)

## Install (copy source)

1. Ensure the theme is installed once per project - tokens.css + base.css imported globally, fonts available. See https://design.freecodecamp.org/registry/theme.md and https://design.freecodecamp.org/registry/starter.md.
2. Copy the files below into `src/ui/heading/` (adjust to your project layout) and import the CSS once from your global stylesheet, e.g. `@import './ui/heading/heading.css';`.
3. Colors, spacing and type come from tokens - tailor the component by editing the copied source; recolour by editing tokens.css, not the component CSS.

## Usage

Pick `level` for the semantic outline, pick `size` for the visual weight.
Decoupling lets you keep the page's H1→H6 structure intact while still
compressing a subheading into something discreet.

## Accessibility

Always use `level` to match the document outline. Size is a presentation
choice; a `<h6 size="display">` is legitimate if the outline requires it.

## Example

```tsx
import { Heading } from './ui/heading/Heading';

<Heading level={1} size="display">Command-line Chic.</Heading>
<Heading level={2} size="xl">Ship interfaces.</Heading>
<Heading level={3} size="lg">Composable primitives.</Heading>
<Heading level={4} size="md">Flat surfaces.</Heading>
<Heading level={5} size="sm">Square corners.</Heading>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `level` | `enum` | no | `2` |  |
| `size` | `enum` | no | `md` |  |

## Source: Heading.tsx

```tsx
import React, { forwardRef } from 'react';

export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
export type HeadingSize = 'display' | 'xl' | 'lg' | 'md' | 'sm';

export interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
  level?: HeadingLevel;
  size?: HeadingSize;
}

function clampLevel(level: number): HeadingLevel {
  if (level < 1) return 1;
  if (level > 6) return 6;
  return level as HeadingLevel;
}

export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
  ({ level = 2, size = 'md', className = '', children, ...rest }, ref) => {
    const safeLevel = clampLevel(level);
    const tag = `h${safeLevel}` as const;
    const classes = ['heading', `heading--${size}`, className]
      .filter(Boolean)
      .join(' ');
    return React.createElement(
      tag,
      { ref, className: classes, ...rest },
      children
    );
  }
);
Heading.displayName = 'Heading';
```

## Source: heading.css

```css
.heading {
  font-family: var(--font-sans, 'Lato', system-ui, sans-serif);
  font-weight: 700;
  line-height: 1.15;
  color: var(--foreground-primary);
  margin: 0 0 0.75rem;
  letter-spacing: -0.01em;
}
.heading--display {
  font-size: var(--fs-display);
  line-height: var(--lh-tight, 1.05);
}
.heading--xl {
  font-size: var(--fs-2xl);
  line-height: var(--lh-tight);
}
.heading--lg {
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
}
.heading--md {
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}
.heading--sm {
  font-size: var(--fs-md);
  letter-spacing: 0;
  line-height: var(--lh-base);
}
```

## HTML / vanilla variant

```html
<h1 class="heading heading--display">Command-line Chic.</h1>
<h2 class="heading heading--xl">Ship interfaces.</h2>
<h3 class="heading heading--lg">Composable primitives.</h3>
```

Interactive behaviours for plain HTML come from the vanilla runtime (data-uikit-* attributes): https://design.freecodecamp.org/registry/vanilla.md - or download https://design.freecodecamp.org/cdn/uikit.global.js once and self-host it (do not hotlink).

## For coding agents

This library is distributed as copyable source, not an npm package. Start at https://design.freecodecamp.org/registry/starter.md, discover components via https://design.freecodecamp.org/llms.txt, and copy files into the consuming project. Keep token names intact; recolour by editing the copied tokens.css.
