Toggle Sidebar B
AppearanceLight & dark mode D

Typeset

A styling system for HTML and rendered markdown, from blog posts to streaming chat. One CSS file you own. There is no component to import; add the .typeset class to a container and everything inside it is styled.

Default

Add typeset to a container and every element inside it is styled. Headings, paragraphs, lists, tables, code, and inline elements all derive their spacing from the same three values, so rendered markdown needs no per-element classes.

Replay preview

Reading rhythm

Typeset styles everything inside the container, so rendered markdown needs no per-element classes. Headings, lists, tables, and code all derive their spacing from three values.

  • Size sets the base text size.
  • Leading sets the space between lines.
  • Flow sets the space between blocks.

Everything else follows from those three.

Inline elements come along for free: bold, italic, links, and inline code.

Rhythm

Three controls carry the whole system. --typeset-size sets the base text size, --typeset-leading sets the line height, and --typeset-flow sets the space between blocks. Heading sizes, list indents, the gap under a heading, and the space around a rule all derive from them. Override them on the container for a one-off, or in a preset class for a whole context.

Replay preview

Roomy

Two ems between blocks, line height at 1.9.

Long-form reading benefits from the extra air.

Tight

Three quarters of an em between blocks, line height at 1.5.

Denser panels and chat surfaces read better this way.

Presets

A preset is a small class that flips the three variables. Three ship with the app: typeset is the default ladder (16px on phones, 18px from sm), typeset-sm is the compact one for dialogs and cards, and typeset-fixed keeps one size at every viewport. They live in main.css next to the format-html block, so adding your own is a matter of one rule.

Replay preview

typeset

Sixteen pixels on phones, eighteen from the sm breakpoint.

typeset-sm

Fourteen pixels with a tighter flow. Use it inside dialogs and cards.

typeset-fixed

Stays at the same size on every viewport. Use it when a pane must not grow.

It fits its container

--typeset-size defaults to 1em, so the surrounding layout decides the scale. Drop the same markup into a chat bubble and it follows the smaller type around it; drop it into an article and it scales up with the page. On small screens it takes a small bump for readability.

Replay preview

Inside a bubble the text follows the smaller type around it.

No override needed.

Inside an article it scales up with the page instead.

format-html

format-html is the legacy container class used by every CMS surface across pmone, pmone-events, and levenium. The vendored typeset.css aliases its container selector to :is(.typeset, .format-html), so the two share one implementation. format-html adds three things on top: a 42rem measure, full-bleed images, and tables that scroll on their own. It sits in @layer components, so a call-site utility such as max-w-none always wins.

Replay preview

Rendered from the database

This container is what every CMS surface uses. It reads the same as typeset but adds the article measure and the wide-table treatment.

Call-site utilities win over it, so max-w-none on the same element widens it without a fight.

Tables

Upstream Typeset expects a typeset-scroll wrapper around wide tables. Database-authored HTML has no such wrapper, so format-html emulates it on the table itself: display block, width max-content, and its own horizontal scrollbar. A plain typeset container keeps the native table layout, which is what the live editor wants.

Replay preview

Wide tables

Tables inside format-html scroll on their own so a wide one never widens the page. Plain typeset containers keep the native table layout instead.

RegionSessionsSignupsConversion
North12,4803182.5%
South9,2404024.3%

Code

Block code takes the muted surface, the mono family, and the radius from your theme. Inline code gets a smaller pill on the same surface, scoped to :not(pre) > code so nested code inside a block is left alone. Syntax-highlighted output keeps its own token colours in both light and dark.

Replay preview

Code

Block code takes the muted surface and the mono family from your theme. Inline code gets a smaller pill on the same surface.

export default defineNuxtConfig({
  css: ["~/assets/css/main.css"]
})

Highlighted output keeps its own token colours in both themes.

Opting out

Add not-typeset (or data-not-typeset) to any subtree that should keep its own styling. The opt-out covers the element and everything under it, so a card grid or an embedded widget can sit inside an article without inheriting document typography.

Replay preview

Opting out

Anything inside the container inherits the document styles.

Card one
Untouched by Typeset.
Card two
Also untouched.

The paragraph after the opt-out picks the rhythm back up.

API Reference

.typeset (container classes)

PropTypeDefaultDescription
typesetclassTurns the styles on. Everything inside the container is styled.
format-htmlclassLegacy alias for the same styles, plus a 42rem measure, full-bleed images, and scrolling tables.
typeset-smclassCompact preset: 14px with a 1em flow. Replaces the old prose-sm.
typeset-fixedclassKeeps one size at every viewport. Replaces the old prose-base.
not-typesetclassOpts a subtree out. data-not-typeset works the same way. Replaces the old not-prose.
typeset-scrollclassUpstream wrapper that makes a wide child scroll. format-html emulates it on tables, so you rarely need it.

CSS custom properties

PropTypeDefaultDescription
--typeset-sizelength1remBase text size. 1em in upstream; the app pins 1rem and steps up at sm via --typeset-size-lg.
--typeset-size-lglengthcalc(var(--typeset-size) * 1.125)Size from the sm breakpoint up. App-specific: upstream ships the inverse ladder.
--typeset-leadingnumber1.75Line height of body text.
--typeset-flowlength1.25emSpace between blocks. Headings and rules derive their spacing from it.
--typeset-heading-weightnumber600App-specific escape hatch. Set it to 500 to restore the lighter headings the typography plugin used to produce.
--typeset-font-bodyfont stackinheritBody family. Leave it alone and it follows your app.
--typeset-font-headingfont stackinheritHeading family. Pinned to inherit on purpose: upstream defaults to var(--font-heading), which is a generic sans in pmone-events and levenium.
--typeset-font-monofont stackvar(--font-mono)Family for code and pre.
--typeset-mutedcolorvar(--color-muted-foreground)Captions, markers, and footnotes. Derived, rarely set by hand.
--typeset-rulecolorvar(--color-border)Rules, table borders, and the blockquote edge.

Accessibility

Keyboard shortcuts and ARIA behavior.

  • Colours, fonts, and radius come from your theme, so dark mode needs no separate rules. There is no dark: variant anywhere in typeset.css.
  • For readers who prefer larger type, expose a roomier preset as a setting: .typeset-large { --typeset-size: 18px; --typeset-leading: 1.9; --typeset-flow: 1.75em }.
  • Typeset sets no max-width. Your layout owns the measure. format-html is the one exception, since every call site relies on it for centring.
  • Headings stop at font-weight 600 and h6 is not uppercased, both deliberate divergences from upstream to match the house type rules.
  • The file is vendored at app/assets/css/typeset.css with a single local patch: the container selector is aliased to :is(.typeset, .format-html). Re-vendoring from source drops that patch, so keep the recipe in the file header.