dembrandt/dembrandt-skills1 file

Modular Scale Typography

Typography feels cohesive and intentional when font sizes follow a modular scale — a ratio-based sequence where every size is mathematically related to the others. Use when defining type scales, setting up design tokens, reviewing font size choices, or when typography feels inconsistent or arbitrary.

Specification
Skill ID
dembrandt/dembrandt-skills/modular-scale-typography
Publisher
dembrandt
Repository
dembrandt-skills
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dembrandt/dembrandt-skills/modular-scale-typographyInstalls these files
  • SKILL.md

What this skill tells the agent

Modular Scale Typography

Typography feels cohesive when all font sizes are related to each other through a single mathematical ratio. Without a scale, sizes get picked arbitrarily and the result feels visually noisy — headings that don't contrast enough, body text too close in size to captions, labels that blend into content.

What Is a Modular Scale

A modular scale starts from a base size and multiplies or divides by a ratio to generate every size in the system.

size(n) = base × ratio^n

Every size is thus a deliberate step away from the base — not a guess.

Choosing a Ratio

RatioNameFeelGood for
1.067Minor SecondVery tightDense data UIs, dashboards
1.125Major SecondSubtleLong-form reading, editorial
1.200Minor ThirdBalancedMost UI applications
1.250Major ThirdClear hierarchyMarketing, landing pages
1.333Perfect FourthStrong contrastDisplay, hero sections
1.414Augmented FourthDramaticPortfolios, branding
1.500Perfect FifthVery dramaticUse sparingly

Default recommendation: 1.25 (Major Third) — enough contrast between steps to feel intentional without being theatrical.

Generating a Scale

Recover an existing scale, don't reverse-engineer it by hand (dembrandt engine, optional). If a brand already has type on the web, get_typography returns the real font sizes, weights, and line-heights computed off the live DOM — infer the underlying ratio from those, then regularise it with the method below, instead of guessing which sizes were intended. See `extract-design`.

Starting from base = 16px, ratio 1.25:

StepFormulaValueRoundedRole
-216 ÷ 1.25²10.24px10pxCaption, label-xs
-116 ÷ 1.2512.80px13pxLabel, small
01616px16pxBody (base)
+116 × 1.2520px20pxBody-lg, lead
+216 × 1.25²25px25pxH4
+316 × 1.25³31.25px31pxH3
+416 × 1.25⁴39.06px39pxH2
+516 × 1.25⁵48.83px49pxH1
+616 × 1.25⁶61.04px61pxDisplay

Round to whole pixels or rem — the ratio provides the intent, exact pixel rounding is fine.

Design Tokens (CSS custom properties)

:root {
  --text-xs:   0.625rem;  /* 10px  — caption */
  --text-sm:   0.813rem;  /* 13px  — label   */
  --text-base: 1rem;      /* 16px  — body    */
  --text-lg:   1.25rem;   /* 20px  — lead    */
  --text-xl:   1.563rem;  /* 25px  — h4      */
  --text-2xl:  1.938rem;  /* 31px  — h3      */
  --text-3xl:  2.438rem;  /* 39px  — h2      */
  --text-4xl:  3.063rem;  /* 49px  — h1      */
  --text-5xl:  3.813rem;  /* 61px  — display */
}

Tailwind Config

// tailwind.config.js
fontSize: {
  'xs':   ['0.625rem', { lineHeight: '1rem' }],
  'sm':   ['0.813rem', { lineHeight: '1.25rem' }],
  'base': ['1rem',     { lineHeight: '1.5rem' }],
  'lg':   ['1.25rem',  { lineHeight: '1.75rem' }],
  'xl':   ['1.563rem', { lineHeight: '2rem' }],
  '2xl':  ['1.938rem', { lineHeight: '2.25rem' }],
  '3xl':  ['2.438rem', { lineHeight: '2.5rem' }],
  '4xl':  ['3.063rem', { lineHeight: '1.1' }],
  '5xl':  ['3.813rem', { lineHeight: '1' }],
}

Why This Makes Typography Feel Cohesive

Without a scale, designers and developers pick sizes by eye or habit (14px, 16px, 18px, 24px, 32px, 48px). These feel subtly wrong because the intervals are uneven — the jump from 14→16 is small, 32→48 is large, and there is no underlying logic tying them together. The eye senses the inconsistency even when the viewer cannot name it.

With a modular scale, every size step carries the same visual weight of change. Hierarchy reads clearly because each level is a proportional step away from the next, not an arbitrary gap.

Minimum Font Size

Body text base: 16px minimum. This is the browser default for good reason — it is the threshold below which reading comfort drops significantly, especially on screens.

  • 16px — standard body text, the default base
  • 14px — acceptable for secondary UI text (labels, captions, metadata) used sparingly
  • Below 14px — do not use. Even at high DPI, sub-14px text fails WCAG contrast requirements for normal text and creates accessibility issues.

In the modular scale, this means the base (step 0) should be 16px, and negative steps (step -1, step -2) should be used only for genuinely secondary content — never for body copy or primary labels.

The 1% heuristic. Sub-16px text is the rare exception (target: under ~1% of a page, never below 14px). Nobody counts characters, so apply it as a role test:

  • May go below 16px — a fixed whitelist of glanced-at roles: timestamps, captions, table metadata, helper text, fine print, badge labels.
  • Never — body copy, primary labels, list item titles, anything actually read to do the task.

Check: scan one screen, count distinct sub-16px roles. Two or three from the whitelist is healthy; five or more — or any reading content — means you've over-shrunk. That's layout density, not a type problem: cut what's shown rather than shrink the type to fit.

Type Rendering Details

Size and ratio set the structure; these details determine whether the type actually reads well on screen.

Letter Spacing

  • Body and default text: keep tracking at 0. Adding it to running text slows reading and makes the type feel loose.
  • Uppercase and small labels: the one place tracking helps, since capitals are visually tight. Cap it at 0.04em, and reach for that only when the label genuinely needs air.
  • Pattern: zero on lowercase body, at most a hair (`≤ 0.04em`) on uppercase labels — never a blanket value. Over-tracking reads as dated, not premium.

Weight on Dark Backgrounds

Light text on dark appears optically thinner — a halation effect where bright type bleeds into the dark field. Step up one weight to compensate: where regular (400) works on light, use medium or semibold for the equivalent text on dark. This keeps perceived weight consistent across modes instead of dark-mode text looking frail.

Monospace

Monospace is for technical content where character alignment matters — code, IDs, numeric tables, diffs. Don't use it as a default UI typeface, and avoid monospace + uppercase (even widths plus tall capitals are hard to scan).

Numbers That Change in Place

A counter, a countdown, a price that updates, a column of figures — proportional digits give each number a different width, so the text jitters sideways on every tick and columns fail to line up. Switch to tabular figures anywhere digits update or stack; leave body text proportional, where tabular digits look gappy.

.stat, .price, td.numeric { font-variant-numeric: tabular-nums; }

Where Lines Break

Headings are the place to control the break. text-wrap: balance evens the lines of a short heading and stops the one-word last line; text-wrap: pretty only prevents the orphan and is the cheaper choice for paragraphs. Balance is capped at a handful of lines by the browser, so it is a heading tool, not a body tool.

Set it once, in the stylesheet, for every element of that kind. A text-balance utility sprinkled on one component holds for that component and silently stops holding on the next page someone builds.

h1, h2, h3, h4, h5, h6,
figcaption, blockquote, dt { text-wrap: balance; }

p, li, dd { text-wrap: pretty; }

Chrome and Safari implement both. Firefox ignores them and falls back to normal wrapping, which is why neither is allowed to be load-bearing.

Never break a line by hand with <br> to fix a widow. It is correct at exactly one viewport width and wrong at every other. Per-component wrapping is a deliberate exception a designer asks for, not a default you reach for.

Use this skill in a project

Open any RiverX project, search the Skills panel for dembrandt/dembrandt-skills/modular-scale-typography, and install it in one click.

Use this skill