# Typography

> A type scale that picks its own element, so the document outline follows the visual one.

`text()` renders a piece of text at one of the library’s sizes. The variant picks a sensible element — `variant: 'h2'` renders an actual `<h2>` — so headings land in the document outline without anyone having to think about it.

## Variants

```javascript
stack({ gap: 'sm' },
  text({ variant: 'h1' }, 'Heading 1'),
  text({ variant: 'h2' }, 'Heading 2'),
  text({ variant: 'h3' }, 'Heading 3'),
  text({ variant: 'h4' }, 'Heading 4'),
  text({ variant: 'h5' }, 'Heading 5'),
  text({ variant: 'h6' }, 'Heading 6'),
  text({ variant: 'lead' }, 'Lead — one step up from body text, for the sentence under a title.'),
  text({ variant: 'body' }, 'Body — the default.'),
  text({ variant: 'small' }, 'Small — captions that are still sentences.'),
  text({ variant: 'caption' }, 'Caption — the small print.'),
  text({ variant: 'overline' }, 'Overline'),
)
```

## Headings

`heading()` takes an outline `level` and sizes itself to match. `size` decouples the two: an `<h1>` that looks like an h3 is still an h1 to a screen reader.

```javascript
stack({ gap: 'sm' },
  heading({ level: 2 }, 'A level-2 heading, sized to match'),
  heading({ level: 2, size: 'h5' }, 'A level-2 heading, sized like an h5'),
)
```

## Tone

Three weights of emphasis, from full contrast down to the quietest readable grey.

```javascript
stack({ gap: 'xs' },
  text('Default — the colour body copy is set in.'),
  text({ tone: 'muted' }, 'Muted — secondary text, still comfortably readable.'),
  text({ tone: 'subtle' }, 'Subtle — labels and metadata.'),
)
```

## Alignment

```javascript
stack({ gap: 'xs' },
  text({ align: 'start' }, 'Start'),
  text({ align: 'center' }, 'Center'),
  text({ align: 'end' }, 'End'),
)
```

## Truncating and clamping

`truncate` cuts a single line with an ellipsis. `lines` clamps to a number of lines instead, which is what a card summary usually wants.

```javascript
stack({ gap: 'md' },
  card({ variant: 'flat' }, cardBody(
    text({ truncate: true }, 'A single line that keeps going well past the width of its container and gets cut off with an ellipsis rather than wrapping.'),
  )),
  card({ variant: 'flat' }, cardBody(
    text({ lines: 2, tone: 'muted' }, 'Clamped to two lines. This paragraph runs on for a while so that there is something for the clamp to actually cut, and then it keeps going a little longer still, past the point where the third line would have started.'),
  )),
)
```

## Inline code and keys

```javascript
text(
  'Run ', code('sitelo build'), ' or press ', kbd('⌘'), ' ', kbd('K'), ' to search.',
)
```

Children are rendered as HTML — that is what makes nesting work everywhere in this library, and `code()` is no exception. So a sample containing tags needs the `text` prop, which escapes them:

```javascript
stack({ gap: 'sm' },
  text(code({ text: '<em>Hello</em>' }), ' — text: shown as written'),
  text(code('<em>Hello</em>'), ' — children: parsed as markup'),
)
```

Both are useful. `text` is for a code sample, where a tag should be read rather than built. Children are for output that has already been syntax-highlighted, where the markup `is` the point — a Prism or Shiki result goes straight in.

```javascript
stack({ gap: 'sm' },
  text(code({ text: 'sitelo build --root docs' })),
  text(code('<span style="color: var(--su-primary-soft-fg)">sitelo</span> build')),
)
```

## Composing

Text takes children, not just a string — so links, code and emphasis nest inside it the same way they would in HTML.

```javascript
text({ variant: 'lead' },
  'Pages are functions that return ',
  code('HTML'),
  '. See the ',
  link({ href: '/docs/pages' }, 'writing pages'),
  ' guide.',
)
```

## Changing the element

`as` overrides the element without changing the look — for a visual heading that must not appear in the outline, or a `<span>` inside a line of text.

```javascript
stack({ gap: 'xs' },
  text({ variant: 'h4', as: 'div' }, 'Looks like a heading, is a div'),
  text({ variant: 'caption', as: 'p' }, 'Caption styling on a paragraph'),
)
```

## Visually hidden

`visuallyHidden()` keeps content in the accessibility tree but off the screen — the label a screen reader needs where sighted readers get it from context.

```javascript
text(
  'Build status: ',
  chip({ color: 'success', dot: true }, 'passing'),
  visuallyHidden(' — last build succeeded 4 minutes ago'),
)
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `'h1'…'h6' \| 'lead' \| 'body' \| 'small' \| 'caption' \| 'overline'` | `'body'` | Size, weight and default element. |
| `tone` | `'default' \| 'muted' \| 'subtle'` | `'default'` | How much contrast the text carries. |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Text alignment. |
| `truncate` | `boolean` | `false` | One line, cut with an ellipsis. |
| `lines` | `number` | — | Clamp to this many lines. |
| `as` | `string` | — | Override the element the variant would pick. |

`heading()` takes `level` (1–6) and an optional `size`; everything else is the same.
