Inset

Typeset

The case for styling rendered markdown with three numbers instead of thirty, and why owning the CSS file beats inheriting a plugin.

NKNabin Khair
3 min read
Typeset

Render markdown and you get plain HTML back: headings, paragraphs, lists, tables, code. None of it has a style. So you write the styles. You write them for the blog. Then you write them again for the docs site. Then a third time for the chat interface that streams responses. Each time you are solving the same problem — how big, how far apart, how tall a line — and each time you start from scratch.

Typeset is the answer to doing that work once.

The problem it solves

Most typography tooling exposes every knob: a dozen font sizes, a leading ratio, tracking, kerning, measure, the space above and below each element. That freedom is the trap. Nobody sets a dozen variables to make markdown look right, so they set three, get something acceptable, and move on — and the next person sets a different three.

Typeset collapses the whole system into three controls and derives everything else from them.

.typeset {
  --typeset-size: 1em;     /* base text size */
  --typeset-leading: 1.75; /* space between lines */
  --typeset-flow: 1.25em;  /* space between blocks */
}

Heading sizes, list indents, the gap under a rule, the room around a blockquote — all of it falls out of those three. Change one and the document follows.

Why a file you own

A plugin ships CSS you cannot read without digging into node_modules. A file in your project is something you can open, change, and understand. When a heading sits too tight against the paragraph below it, you do not need to learn a modifier API — you find the rule and adjust it.

<article className="typeset typeset-docs">{content}</article>

typeset turns the styles on. typeset-docs is a preset — a named set of the three values, tuned for a context. You can keep several in the same app: a tight one for chat, a roomy one for long articles.

Streaming changes the rules

Most typography CSS uses :last-child and :has() to clean up spacing. That works for a finished document and breaks the moment content streams in. A new block arrives, the previous block is no longer last, and its bottom margin disappears — the whole page jumps.

Typeset avoids forward-looking selectors in its layout rules. Spacing flows in one direction with margin-block-start. A new block brings its own space and never restyles the blocks above it. The page is stable while it grows.

The trade

You give up the long menu of variables. You get a system that is small enough to hold in your head, that follows your theme's colors and radius, and that you can change without reading documentation. For most rendered text, that is the trade you want.

Comments(5)

YO

Supports Markdown formatting.

  • MC
    Maya Chen

    The concentric radius tip alone was worth the read. I’ve been matching outer and inner radii for years without realizing why it felt off.

    outer = inner + padding — that’s the whole rule.

    • SO
      Sam Ortiz

      Same — once you see it, you can’t unsee the mismatch on nested cards.

      • Cards
      • Dialogs
      • Comment threads (ironically)
      • MC
        Maya Chen

        Outer = inner + padding is the whole rule.

        Exactly. Glad this landed.

    • RN
      Riley Ng

      Also useful with Typeset measure caps — nesting and measure fight each other if you’re not careful.

  • JB
    Jordan Blake

    Clear writing on a topic that usually gets hand-waved. Would love a follow-up on how this plays with nested dialogs.

Related posts