# Heading

> Heading component for React and shadcn: size, weight, tracking and leading set together from 16 to 60px, separate from the h1–h6 level.

A heading whose size, weight, tracking and leading are bound together. Level sets the outline and size sets the look, from 16 to 60px, with display tracking from 36px.

- Tier: Free (MIT)
- Interaction: Type
- Page: https://eased.dev/components/heading
- Registry: https://eased.dev/r/heading.json

## Install

```bash
npx shadcn add https://eased.dev/r/heading.json
```

Get a heading whose size, weight, tracking and leading always match, with the document outline (`level`) set apart from the look (`size`).

## When to use

- Keep page and section titles consistent: a 36px title always gets display tracking, and a 20px one never goes semibold.
- Keep the outline correct when the design wants a different size: render an `h2` that looks like a 20px title.
- Mark a quieter heading under a stronger one with `tone="secondary"`.

## When not to use

- Body copy, labels and metadata: use [Text](/components/text).
- Headings inside rendered markdown or rich text: wrap the output in [Prose](/components/prose), which styles its `h1`–`h6` for you.
- A form control's name: use [Label](/components/label).

## Usage

```tsx billing-header.tsx
import { Heading } from "@/components/eased/heading/heading";
import { Text } from "@/components/eased/text/text";

export function BillingHeader() {
  return (
    <header className="flex flex-col gap-1">
      <Heading level={1}>Billing and invoices</Heading>
      <Text tone="secondary">Manage your plan, payment methods and receipts.</Text>
    </header>
  );
}
```

## Examples

### Level and size apart

Use `size` when the outline needs an `h2` but the layout needs a smaller title, such as a card heading.

```tsx
<Heading level={2} size="xl">Payment methods</Heading>
```

### Secondary tone

Use `tone="secondary"` for a heading that sits under a stronger one, such as a group label inside a settings page.

```tsx
<Heading level={3} size="md" tone="secondary">Billing contact</Heading>
```

## API reference

### Heading

| Prop | Type | Default | Description |
|---|---|---|---|
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | The element it renders, `h1` to `h6`. |
| `size` | `"md" \| "lg" \| "xl" \| "2xl" \| "3xl" \| "4xl" \| "5xl" \| "6xl"` | by level | 16, 18, 20, 24, 30, 36, 48 or 60px. Without it, level 1 is `4xl`, 2 is `2xl`, 3 is `xl`, 4 is `lg`, and 5–6 are `md`. |
| `tone` | `"default" \| "secondary"` | `"default"` | `secondary` uses the secondary text colour. |
| `className` | `string` | `""` | Appended to the heading's classes. |

Every other `h1`–`h6` attribute passes through, such as `id` for an anchor link.

Each size carries its own weight: medium below 24px, semibold from 24px, and the display face with `tracking-display` from 36px. Sizes use the `text-display-*` and `text-ui-*` tokens in `eased.css`.

## Accessibility

- It renders a real `h1`–`h6`, so the outline that screen readers navigate by follows `level`, never `size`.
- Keep levels in order on a page; change `size`, not `level`, to make a heading look smaller.

## Responsive

Sizes are fixed rem values and don't change with the viewport. Long titles wrap with `text-balance`, so the last line is never a single word, and long words break instead of overflowing. The heading has `min-w-0`, so it shrinks inside flex and grid rows; add `truncate` through `className` to keep it on one line.

## Motion

None. A heading holds still so the things around it can move.

## Notes for AI

Pick Heading for any title outside rendered markdown. Set `level` from the document outline first, then set `size` only when the design calls for a different look. Don't skip levels to get a smaller size.

Don't add `font-semibold`, `tracking-*` or `text-*` size classes: the size already pairs weight, tracking and leading, and extra classes break that pairing. Don't use it for headings inside markdown output; Prose styles those.

## Related

- [Text](/components/text): body text on the 11–18px UI scale.
- [Prose](/components/prose): styles headings and everything else inside rendered markdown.

## Example

`components/eased/heading/heading-demo.tsx`

```tsx
import { DemoFrame } from "../lib/demo-frame";
import { Heading, type HeadingSize } from "./heading";

const SCALE: { size: HeadingSize; px: number; text: string }[] = [
  { size: "5xl", px: 48, text: "Ship faster" },
  { size: "4xl", px: 36, text: "Quarterly review" },
  { size: "3xl", px: 30, text: "Revenue by region" },
  { size: "2xl", px: 24, text: "Billing and invoices" },
  { size: "xl", px: 20, text: "Payment methods" },
  { size: "lg", px: 18, text: "Visa ending 4242" },
  { size: "md", px: 16, text: "Billing contact" },
];

/** The heading scale: each size brings its own weight, tracking and leading. */
export function HeadingDemo() {
  return (
    <DemoFrame title="Type scale" meta="Geist">
      <ol className="grid min-h-0 flex-1 grid-cols-[2.5rem_minmax(0,1fr)] content-center items-baseline gap-x-3 gap-y-3 overflow-y-auto px-(--pad) py-4">
        {SCALE.map(({ size, px, text }) => (
          <li key={size} className="col-span-full grid grid-cols-subgrid items-baseline">
            <span className="font-mono text-ui-xs text-text-muted tabular-nums slashed-zero">{px}</span>
            <Heading level={3} size={size} className="truncate">
              {text}
            </Heading>
          </li>
        ))}
      </ol>
    </DemoFrame>
  );
}
```
