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.
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-sizesets the body size. It follows the surrounding context.--typeset-leadingsets the distance between lines.--typeset-flowsets the space between blocks. Typeset derives heading and rule spacing from it.
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
--foregroundand--muted-foreground. - Links use
--primary. - Inline code uses
--muted,--border, and--radius-sm. - Rules, table separators, and quotes use
--borderor--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.
Interactive docs components opt out of Typeset so their own masters keep control:
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.
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:
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:
Keep colors, radius, spacing roles, and component states in the shared theme contract. Use Typeset presets only for reading rhythm.