---
title: Typography
description: Flexible prose styles for rendered HTML and markdown.
group: Components
order: 72
---

Style headings, paragraphs, lists, and more inside a `typeset` container. When prose includes interface blocks such as [Card](/docs/components/card), opt them out of Typeset as needed.

```tsx
export function Example() {
  return (
    <div className="typeset typeset-docs">
      <h1>Typeset</h1>
      <p>
        A styling system for HTML and rendered markdown. One CSS file you own —
        wrap content in <code>typeset</code> and the rhythm follows.
      </p>
      <h2>Rhythm</h2>
      <p>
        Three controls drive everything: <strong>size</strong>,{" "}
        <strong>leading</strong>, and <strong>flow</strong>. Headings, lists,
        and rules derive from them.
      </p>
      <ul>
        <li>Fits its container</li>
        <li>Uses your theme tokens</li>
        <li>Stable for streaming appends</li>
      </ul>
      <blockquote>
        <p>Spacing flows with margin-block-start only — new blocks do not restyle earlier ones.</p>
      </blockquote>
    </div>
  )
}
```

## Installation

Install the package, then import Typeset after Tailwind.

```bash
npm i @arctis-sh/ui@latest
```

```css
@import "tailwindcss";
@import "@arctis-sh/ui/styles/typeset.css";
```

Wrap your rendered HTML or markdown.

```tsx
<div className="typeset typeset-docs">
  <YourMarkdownRenderer>{content}</YourMarkdownRenderer>
</div>
```

`typeset` enables the styles. `typeset-docs` and `typeset-chat` are presets included with the stylesheet.

## Principles

Three controls set the rhythm: **size**, **leading**, and **flow**. Heading sizes, list indents, and spacing around rules all derive from them.

## Features

- **Fits its container.** Chat bubbles inherit smaller type while articles scale up. Below `48rem`, the size gets a small readability bump.
- **Uses your theme.** Colors and radius come from `--color-*` and `--radius`. Dark mode follows your tokens.
- **Easy to tune.** Change a preset and the whole document follows.
- **Streaming-safe.** New blocks do not restyle earlier ones (no `:last-child` / `:has()` in layout rules; `margin-block-start` only).

## Custom Typesets

Defaults live on `.typeset`. Override with a preset class:

```css
.typeset-chat {
  --typeset-flow: 1em;
  --typeset-leading: 1.6;
}

.typeset-docs {
  --typeset-size: 15px;
  --typeset-flow: 1.5em;
}
```

```tsx
export function Example() {
  return (
    <div className="flex flex-col gap-8">
      <div>
        <p className="mb-2 text-xs tracking-wide text-muted-foreground">
          typeset-docs
        </p>
        <div className="typeset typeset-docs">
          <h3>Docs rhythm</h3>
          <p>Roomier flow for articles and documentation.</p>
        </div>
      </div>
      <div>
        <p className="mb-2 text-xs tracking-wide text-muted-foreground">
          typeset-chat
        </p>
        <div className="typeset typeset-chat">
          <h3>Chat rhythm</h3>
          <p>Tighter leading and flow for message bubbles.</p>
        </div>
      </div>
    </div>
  )
}
```

For a one-off adjustment, override the variable directly.

```tsx
<article className="typeset [--typeset-flow:1.75em]">...</article>
```

## Responsive Table

Tables wrap by default. For horizontal scrolling, place one inside `typeset-scroll`.

```tsx
<div className="typeset-scroll">
  <table>...</table>
</div>
```

## Overrides

Typeset lives in the `components` layer and uses `:where()` selectors, so Tailwind utilities take precedence without `!important`.

```tsx
<div className="typeset typeset-docs">
  <p className="text-lg">...</p>
</div>
```

## Opting Out

Add `not-typeset` or `data-not-typeset` to keep a subtree unstyled:

```tsx
import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function Example() {
  return (
    <div className="typeset typeset-docs">
      <p>Styled prose sits in the typeset container.</p>
      <Card className="not-typeset mt-4">
        <CardHeader>
          <CardTitle>Untouched card</CardTitle>
          <CardDescription>
            Opt out with not-typeset or data-not-typeset.
          </CardDescription>
        </CardHeader>
      </Card>
    </div>
  )
}
```

```tsx
import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@arctis-sh/ui/card"

<div className="typeset">
  <p>Styled prose.</p>
  <Card className="not-typeset">
    <CardHeader>
      <CardTitle>Untouched card</CardTitle>
      <CardDescription>
        Opt out with not-typeset or data-not-typeset.
      </CardDescription>
    </CardHeader>
  </Card>
</div>
```

## Streaming

- No forward-looking selectors in layout rules
- Spacing uses `margin-block-start` only
- Table separators sit on cells so appending a row does not restyle the row above

## API Reference

Rhythm and font variables on `.typeset` (and presets that override them).

| Variable | Type | Default |
| --- | --- | --- |
| --typeset-font-body | font-family | inherit |
| --typeset-font-heading | font-family | var(--font-sans) |
| --typeset-font-mono | font-family | ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace |
| --typeset-size | length | 1em |
| --typeset-leading | number | 1.75 |
| --typeset-flow | length | 1.25em |

```tsx
<div className="typeset typeset-docs">
  <YourMarkdownRenderer>{content}</YourMarkdownRenderer>
</div>
```
