Capítulo 5 de 108

Typeset

Core Idea

shadcn/typeset is one owned CSS file that styles all rendered HTML/markdown (blog, docs, chat) inside a typeset container, controlled by three variables (size, leading, flow) instead of styling every element by hand.

Key Concepts

  • typeset class: turns the styling on for its subtree; combine with a preset class (e.g. typeset-docs).
  • Rhythm (three controls): --typeset-size (base font-size, default 1em), --typeset-leading (line-height), --typeset-flow (space between blocks) — everything else (heading sizes, list indent, rule spacing) derives from these.
  • Container-aware sizing: fits its container (shrinks in a chat bubble, scales in an article), unlike a fixed rem scale.
  • Theme-aware: colors, fonts, radius, and dark mode come from the app's existing theme tokens, nothing separate to configure.
  • not-typeset / data-not-typeset: opt a component and its subtree out of Typeset styling (equivalent to @tailwindcss/typography's not-prose).
  • typeset-scroll: wrapper to make a wide table/block scroll horizontally instead of wrapping.
  • Streaming stability: avoids forward-looking selectors (:last-child, :has(), :empty) and only uses margin-block-start, so appending a new block never restyles earlier ones.

Code Examples

@import "tailwindcss";
@import "./typeset.css";
<div className="typeset typeset-docs">
  <YourMarkdownRenderer>{content}</YourMarkdownRenderer>
</div>
  • O que demonstra: setup mínimo, import do CSS + wrapper com preset.
.typeset-chat {
  --typeset-flow: 1em;
  --typeset-leading: 1.6;
}
.typeset-docs {
  --typeset-size: 15px;
  --typeset-flow: 1.5em;
}
  • O que demonstra: múltiplos presets no mesmo app (chat compacto vs. docs espaçoso), trocados por classe.
<div className="typeset">
  <p>Styled prose.</p>
  <Card className="not-typeset">Untouched component.</Card>
</div>
  • O que demonstra: opt-out de um componente dentro do container tipográfico.

Reference Tables

@tailwindcss/typography (prose)Typeset
SizingFixed rem scale (prose-sm...prose-2xl)Relative to container, any size
Dark modeprose-invert, second paletteTheme tokens flip automatically
ThemingProse color vars, scale baked inApp theme tokens + font/rhythm controls
Overridesprose-a:, prose-headings: modifiersPlain Tailwind utilities/CSS win (:where(), zero specificity)
StreamingNo append-stability contractDesigned for stable appends
Distributionnpm plugin, generated CSSOne CSS file you own

Anti-patterns

  • Setting a max-width inside typeset.css: Typeset deliberately leaves measure/width to your layout (or the builder's "Measure" control on the wrapper), not baked into the stylesheet.
  • Using :last-child/:has()-style rules when customizing: breaks streaming stability, causes earlier blocks to restyle as new ones arrive.

Key Takeaways

  1. Three variables (size, leading, flow) drive the whole rhythm; tune those before reaching for per-element overrides.
  2. Build/generate the file via the visual typeset builder, then own and edit typeset.css directly in the repo.
  3. Multiple presets can coexist for different contexts (chat vs docs vs reading mode); apply per-container.
  4. Plain Tailwind utilities and normal CSS selectors override Typeset without needing !important, since it lives in the components layer with :where().

Connects To

  • theming: Typeset consumes the same color/radius theme tokens described there.