Skip to content
ghostui

Typeset

One owned CSS contract for HTML and rendered Markdown: theme-aware type, stable rhythm, and context presets.

Typeset is Ghost UI's reading system for HTML and rendered Markdown. It follows the app theme and exposes three rhythm controls: --typeset-size, --typeset-leading, and --typeset-flow. All heading sizes, list indents, and block spacing derive from those controls.

The implementation lives in design-system/typeset.css. It is imported after the token sheet. There is no typography plugin and no page-specific alias layer.

Container and preset

Add typeset to the content container. Add a preset when the context needs a different rhythm. typeset-docs is the Ghost UI docs preset.

app.css

The layout owns the reading measure. Typeset does not set a maximum width, so the same content contract works in a docs article, chat message, or card.

Heading follows the host theme

Paragraphs, lists, links, code, quotes, and tables share one rhythm.

The three controls

Keep the public tuning surface small:

  • --typeset-size sets the body size. It follows the surrounding context.
  • --typeset-leading sets the distance between lines.
  • --typeset-flow sets the space between blocks. Typeset derives heading and rule spacing from it.
typeset-presets.css

For a one-off adjustment, set a value on the container. Do not add a new semantic type token for one page.

Roles and theme inheritance

Typeset inherits Ghost UI's existing roles:

  • IBM Plex Sans is the heading and body family through --font-sans.
  • Monospace is reserved for code through --font-mono.
  • Text uses --foreground and --muted-foreground.
  • Links use --primary.
  • Inline code uses --muted, --border, and --radius-sm.
  • Rules, table separators, and quotes use --border or --ring.

No --ink, --paper, --canvas, --surface, or target-specific gray is needed. The semantic roles flip with dark mode and follow the active neutral palette.

Body text and links use semantic roles.

--foreground and --muted follow the theme.

Elements and overrides

Typeset styles headings, paragraphs, lists, links, blockquotes, inline code, preformatted code, and tables. Its element selectors use :where(), so normal utilities and component CSS can win without !important.

content.tsx

Interactive docs components opt out of Typeset so their own masters keep control:

component-boundary.tsx

Ghost's Preview, CodeBlock, and API table masters use this boundary. Their spacing as blocks still belongs to the surrounding Typeset flow.

Tables and code

Tables remain real tables. Wrap a wide table in typeset-scroll when horizontal scrolling is preferable to wrapping.

table.tsx

Code blocks can stream and grow. The docs master owns copy, disclosure, and syntax presentation; Typeset only provides the surrounding content contract.

Streaming stability

Typeset spaces blocks with forward-only margin-block-start. It does not use :last-child, :has(), or :empty for layout. Adding a block therefore does not restyle the blocks that came before it.

This matters for chat and streamed Markdown. A newly received paragraph gets its own leading margin. Earlier paragraphs keep their geometry.

Accessibility and responsive behavior

Typeset follows the host color tokens in light and dark mode. It keeps semantic headings and real tables, and it does not hide overflow by default. Keep focus styles on interactive content. Expose a larger preset when readers need more space:

typeset-accessibility.css

Use typeset-large as a user setting, not as a replacement for the default docs preset. Respect prefers-reduced-motion in interactive content around the Typeset container.

Custom contexts

Presets are appropriate when the content context changes. They are not a second design system:

typeset-contexts.css

Keep colors, radius, spacing roles, and component states in the shared theme contract. Use Typeset presets only for reading rhythm.