measured-type

agent
Security Audit
Fail
Health Warn
  • License — License: NOASSERTION
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Fail
  • exec() — Shell command execution in evals/0008/analyze.js
  • fs module — File system access in evals/0008/analyze.js
  • exec() — Shell command execution in evals/0009/analyze.js
  • fs module — File system access in evals/0009/analyze.js
  • exec() — Shell command execution in evals/0010/analyze.js
  • fs module — File system access in evals/0010/analyze.js
  • exec() — Shell command execution in evals/2x2/analyze.js
  • fs module — File system access in evals/2x2/analyze.js
  • exec() — Shell command execution in evals/h2h-grade.js
  • fs module — File system access in evals/h2h-grade.js
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Agent skill for web typography: Bringhurst applied to modern CSS, with claims measured in the browser and a baseline eval suite

README.md

Measured Type. Type, with proof. An agent skill for considered web typography.

Agent skill: skills CLI, Claude Code and gh skill
Evals: 5 development, 3 held-out and 6 head-to-head cases
License: CC BY-NC 4.0 text, MIT code
Last commit
GitHub stars

Install Where it helps Evidence Read the skill Support License

Measured Type is an agent skill for web typography: Bringhurst's rules
translated into current CSS and scripts that measure what can be measured
(line length, zoom growth, contrast, figures in a number column, font
coverage) in a real browser or in the font file. It is for design engineers,
frontend developers and design-system teams who let agents set and review
text.

The word Type at 100% and 200% browser zoom on a 1280px window. Body text in fixed rem grows 2.00 times. A fluid hero, clamp(2.5rem, 2.0833rem + 1.8519vw, 3.75rem), written without the skill, grows only 1.58 times. The hero written with the skill, clamp(3.25rem, 2.9167rem + 1.4815vi, 4.25rem), grows 1.71 times.

At 200% zoom on a 1280px window 100% 200% Growth
Body text, 1rem 16.0px 32.0px 2.00×
Hero written without the skill 57.0px 90.4px 1.58×
Hero written with the skill 65.6px 112.3px 1.71×

Zoom doubles rem but halves the CSS viewport, so the viewport term in a
fluid size stops growing. In the development benchmark both runs without the
skill shipped heroes like the second row; with it, both did the ratio math
first. WCAG 1.4.4 sets no ratio of its own; the 1.6× bar is this project's.

Where it helps, what it costs, when to skip it

Where it helps

  • Line length in a wide face: 66ch in Verdana sets 81 characters; the
    skill counts lines instead of trusting the unit, and came closer to 66
    than a model given a one-sentence hint.
  • Fluid type under zoom when the brief fixes the sizes: type-scale.js --fit gives two forms that keep both sizes and pass the zoom bar. On a
    held-out case every run with the skill met all three checks against none
    without it; a one-sentence hint did as well there (see
    Evidence).
  • Right-to-left audits that convert text flow but leave decoration and
    already-mirrored glyphs alone.
  • Dense product UI: tabular figures, numbers aligned at the end, tracked
    capitals, truncated text, contrast in both colour schemes.
  • Agents without a browser of their own: the bundled Chromium measures, and
    without it the tools say what they could not check.
  • Teams: one report format with rule IDs, severities and a Block or Approve
    verdict that can gate a pull request.

What it costs

  • 7% to 25% more tokens and up to twice the time per task, measured across
    every experiment in Evidence.
  • npm install and a Chromium download for the measuring scripts (the
    linter and the estimate need neither).
  • The text is CC BY-NC 4.0; whether paid client work may use it is not
    settled (License).

When to skip it

  • Fluid type under zoom on its own: one sentence ("check that it still grows
    at least 1.6× at 200% zoom") matched the skill on the held-out case; the
    skill pulled ahead only on the wider 1.8× range of the hero case, where the
    sentence's runs gave up the desktop size.
  • One-off questions a current model already answers well.
  • Logos, drawing icons or fonts, build setup.

It rarely loads on its own (about one run in ten): name it when you want it.

How it works

  1. Classify the text. Reading, display and interface text follow
    different rules; book rules applied to a button are their own mistake.
  2. Direction and language first. Every rule is flow-relative, with lang
    and dir in place, so an RTL or vertical edition is a content change.
  3. Decide with the rules. Bringhurst's The Elements of Typographic Style,
    numbered so reviews can cite §2.1.2, translated into CSS that ships today.
  4. Measure what can be measured, and when no browser is available,
    estimate and say so. Anything a check cannot compute is listed under Not
    verified, never passed in silence.
  5. Review with a point of view. A taste reference for what reads as
    considered and the template tells that read as defaults.

Install

skills CLI (skills.sh), as a personal skill in every
project:

npx skills add PetriLahdelma/measured-type --skill measured-type --agent claude-code -g

Leave out -g to install into the current project, or --agent to choose
other agents. Tested with skills 1.7.4 on macOS, 29 October 2026. Without the
CLI, copy skills/measured-type from a clone into ~/.claude/skills/, or use
gh skill install PetriLahdelma/measured-type measured-type (preview).

The measuring scripts are optional; without them the skill advises but
cannot measure:

cd ~/.claude/skills/measured-type && npm install && npx playwright install chromium
pip install fonttools brotli

Check the install with npm test in the same folder. A few tests skip in
an installed copy because their fixtures live in this repository; the
estimate calibration skips where its macOS fonts are not installed.

Use it

Name the skill. A first task, in a project with a stylesheet:

Use measured-type to audit the typography in styles.css and fix the HIGH findings. Measure the line length in the browser.

Tasks it is written for:

  • Set the article body for our docs in Verdana at 1.125rem, about 66 characters per line.
  • Build a type scale for this site that still works at 200% zoom.
  • Audit this stylesheet before we ship the Arabic edition.
  • Does our italic have real small caps?
  • The numbers in our admin table don't line up and the status badges are hard to read.

It works alongside general frontend and design-system skills: they keep layout
and components, this one makes the type decisions inside them.

Without installing anything. The linter and the estimate need only Node:

node ~/.claude/skills/measured-type/scripts/estimate-measure.js "Verdana" 66ch
about 81 characters per line in Verdana at 66ch (estimate from a Chromium calibration, not a measurement)
  671.6px at 16px; average character 0.504em, zero 0.636em

What it measures

Script What it does
page-audit.js Audits a rendered page at 1280, 768 and 375px and at 200%, 400% and 500% zoom: reflow, characters per line, contrast in light and dark schemes, the WCAG 1.4.12 spacing override, fonts that fell back, zoom growth and the screen and product UI checks. --lite lints instead when there is no browser
lint-css.js Lints CSS, SCSS, Less, HTML, component style blocks and Tailwind classes (also inside cn(), clsx(), Vue :class and Svelte class:), or only what a branch changed (--diff). Fluid sizes are evaluated at zoom. No dependencies
measure-line.js Counts characters per line of one selector, graphemes and columns counted correctly
estimate-measure.js Estimates characters per line without a browser, from a Chromium calibration of 30 faces
font-features.py Reports which OpenType features a font file really covers, per file (roman and italic can differ)
fallback-metrics.py Builds a metric-matched fallback @font-face, so the web font swaps in without reflow
type-scale.js Prints fluid type tokens, each checked for range, zoom growth and step ratio

Every script was written test first (specs/); both audits
report in one format (references/audit.md).
The audit of the ops dashboard from head-to-head 2 and 3
(evals/h2h-dashboard/), with the Fix column
and half the rows left out:

Severity Rule Location Measured
HIGH MT-CONTRAST p.foot 2.23:1 (rgb(170, 170, 170) on rgb(250, 250, 250), needs 4.5:1)
HIGH MT-REFLOW table.services at 320 CSS px (400% zoom of 1280) page is 924px wide in a 320px viewport
MEDIUM MT-TABULAR table.services columns 3, 4 and 5 1 and 0 differ by 26.8% in width, so digits do not line up
LOW MT-CAPS-TRACKING p.eyebrow, p.kpi-label, th capitals at 11px with letter-spacing 0px (under 0.03em)
LOW MT-NUM-ALIGN table.services columns 3, 4 and 5 numbers of different lengths set text-align: left
LOW MT-TRUNCATE td > span.name "Authentication gateway for partner accounts" cut at 1280px
LOW MT-UI-SIZE p.foot 10px interface text (house floor 12px)

Verdict: Block (9 HIGH).

Product UI fails differently from an article:
nothing is too long to read, but it is hard to compare. Proportional figures
cannot line up, so 411 108 and 80 011 end at different edges even in a
column of their own:

The same four-row service table set twice in system-ui at 15px. With the defaults, start-aligned proportional figures: the digit 1 is 26.9% narrower than 0 and the last digits of the Requests column end across 434 to 447px. With text-align: end and tabular-nums: 0% difference and every last digit at one position.

More proof

ch is the width of the zero. The same 66ch sets different measures in
different faces, so the skill counts the rendered lines:

Two paragraphs set at max-inline-size: 66ch and 16px. In Verdana the box is 671px wide and the full lines average 81 characters. In Source Serif 4 the box is 548px wide and the lines average 70 characters. 1ch is the width of the zero: 0.64em in Verdana, 0.52em in Source Serif 4.

max-inline-size: 66ch at 16px Verdana Source Serif 4
Width of the zero, 1ch 0.64em 0.52em
Box width 671px 548px
Characters per full line 81 70

Asked for about 66 characters per line in Verdana, the skill set the box in
em and checked it with measure-line.js: 66.1 characters per line, range 60
to 71, in a box of 34.5em (54.3ch).

A feature tag is not coverage. font-features.py reads the file: Source
Serif 4's roman has complete small caps and its italic has none, although
both files list the same family.

The report for the italic
# Source Serif 4 Italic  (SourceSerif4-Italic[opsz,wght].ttf)
italic: yes
default figures: tabular lining
GSUB: aalt case ccmp dnom frac liga lnum locl numr onum ordn pnum sinf ss01 ss02 ss03 subs sups tnum zero

  smcp: ABSENT  (small caps from lowercase: should cover a-z and accented letters)
  c2sc: ABSENT  (small caps from capitals: should cover A-Z and accented capitals)

check smcp: absent
  missing: abcdefghijklmnopqrstuvwxyzäöå

One article set twice. The "before" uses template defaults; no model
wrote it:

The same article set twice at a 1280px window. Before, with template defaults: 160 characters per line, a heading that grows 1.00 times at 200% zoom, justified text, hyphen-minus for negative numbers and table figures left to the font. After, set with Measured Type: 65 characters per line, 2.00 times, ragged hyphenated text, U+2212 minus signs and declared tabular lining figures.

examples/editorial/ holds both pages and a table of
every change with the section it follows. The audit blocks before.html with
4 HIGH findings and approves after.html.

In CI and pull requests

The linter exits 1 when a HIGH finding remains, so it can gate a pull request
with no install. --diff limits it to what the branch changed, which lets an
older codebase adopt it gradually:

node path/to/measured-type/scripts/lint-css.js src --diff origin/main

The rendered audit needs Playwright and a page: point it at a preview
deployment or a built HTML file. Exit codes for both: 0 approve, 1 block, 2
usage error.

Reference, by decision

You are deciding Read
How wide a column of text should be Choose a comfortable measure, then measure-line.js
Leading and the space between blocks Vertical space in measured intervals
A type scale that survives zoom Building or reviewing a type scale and Don't compose without a scale
Whether a font has real small caps or text figures Checking what a font contains, then font-features.py
Spacing for capitals, labels and acronyms Letterspace capitals and small caps
Data tables, labels, badges and status text Dense product UI
Inline code, icons beside labels, heading order, pairing faces, kerning, thin weights Screen and interface type
Where hard spaces go Link short numerical expressions
Making a layout work right to left Physical to logical map and Mirroring
Quotes, decimals, units and dates for a locale Locale conventions, worked for English, Finnish and Swedish
Replacing CSS that was renamed or never shipped Renamed, dead or wrong
Whether the result looks considered Reviewing with a point of view

Evidence

The head-to-heads ran on Claude Opus 5.5 on 29 October 2026 (head-to-head 3
added Sonnet 5.5), two runs per cell; the 2×2 and spec 0008 ran on Sonnet 5.5
on 30 October, five and then ten runs per cell. Cases and graders were committed before the runs, and
every result is reproducible from the committed run files.

Experiment What ran With the skill Without What it shows
Development set 5 cases written with the skill 78/78 65/78 The content reaches the agent; most misses are house conventions, three are errors a reader would see
Held-out 3 cases written after the content was frozen 34/34 33/34 No measurable difference
Head-to-head 1 3 cases, 7 configurations 40/40 39/40 A tie with frontend-design at 40/40 under the corrected graders; one assertion is noise
Head-to-head 2 Dense dashboard and a 66-character measure, browser offered to all 24/24 24/24 With a browser, a model without the skill measures and fixes the same faults
Head-to-head 3 Same cases, no browser in the brief, Opus and Sonnet 48/48 48/48 Runs with the skill landed on 66 characters per line, two without it at 62.5 and 63.4; the skill's runs had its bundled Chromium, so the gain comes from the tooling
2×2 Skill and browser varied independently, 66-character measure and a fluid hero under zoom, Sonnet, 40 runs 51/60 50/60 Line length follows the browser; on the hero the skill raised growth at 200% zoom with or without one (1.53× against 1.43× to 1.45×), though 9 of its 10 runs still missed the 1.6× bar
Spec 0008 A one-sentence hint against the skill, and the 2×2 at ten runs per cell, Sonnet, 80 more runs 101/120 106/120 with the hint, 100/120 with nothing The hint beat the skill on growth under zoom (1.63× against 1.50×), mostly by giving up the requested 72px; the skill came closer to 66 characters per line (0.25 against 1.54 off, with a browser)
Spec 0009 The hero case again after type-scale.js --fit, Sonnet, 20 runs 20/20 met all three checks 6/20 with the hint, 0/20 with nothing Every run used the two-slope form --fit printed and kept both requested sizes (p < 0.001 against the skill before it); a development result on the case that motivated the change
Spec 0010 Held-out twin of the hero case (48 to 80px, new page and prompt), Sonnet, 30 runs 10/10 met all three checks 10/10 with the hint, 0/10 with nothing A held-out gain over no skill (p < 0.0001); no detectable difference from one sentence, whose runs mostly reached 80px early and grew a little more
Triggering 16 typography prompts, 16 near misses, 3 runs each 5 of 48 loads Rarely loads on its own; never loads for a near miss

How to read it. The 2×2 and spec 0008 separate the skill's guidance from
its measuring tools and from one sentence of advice. On line length the
browser does most of the work, and the skill adds a little on top: with a
browser its runs came within a quarter of a character of 66 on average,
against 0.68 off without it (p = 0.031, ten runs per cell) and 1.54 off with
the hint (p = 0.0001); without a browser its estimator beat a model guessing
by about one character (p = 0.010). Every run of every kind was inside the
graded band. On the fluid hero the skill raised growth under zoom (1.50×
against 1.43×, p < 0.0001), but one sentence naming the 1.6× bar raised it
further (1.63×, p < 0.0001); 14 of the 20 runs with that sentence reached
the bar by giving up the 72px desktop size the task asked for, and no
configuration reliably met both until spec 0009 added type-scale.js --fit,
after which all 20 runs with the skill met both; that is the tool printing
the answer, measured on the case that motivated it. On a held-out twin (48 to
80px) the skill again met all three checks in 10 of 10 runs against 0 of 10
without it, and the one-sentence hint matched it (10 of 10). The bar is this
project's own convention.
Before these experiments, the guidance changed nothing measurable on the
cases written after the content was frozen. The development set shows that
the content reaches the agent, but the references contain worked answers for
some of those cases; most of the baseline's misses there are conventions this
project chose (the 1.6× zoom bar, the 1.15× step floor, spacing from the body
line). Not yet tested: models other than Claude, and cases beyond these two
for the hint.

Each results folder has the method, every run's output, grade and timing, and
every correction and incident. How to run the cases yourself:
evals/README.md.

Pairs with Rhythmguard

Rhythmguard,
a Stylelint plugin by the same author, keeps every spacing value (and, opt in,
every type size) on your scale. Measured Type decides whether the scale reads
well. They meet only at the type scale: type-scale.js --prefix font-size
writes tokens Rhythmguard reads as its typography scale, and its README has
the config.

On one stylesheet, run with Stylelint 17.16 and Rhythmguard 3.14.0, each tool
finds what the other leaves alone:

Finding Reported by
padding: 13px and font-size: 17px, off the scale Rhythmguard
font-size: 3vw, which never doubles under zoom Measured Type
text-transform: uppercase with no tracking Measured Type
max-inline-size: 66ch in Verdana, about 81 characters per line Measured Type

Identity

Generated illustration: a light reading page on a desktop display. The measured type wordmark sits above the headline Reading is a rhythm, set large in a serif with rhythm in italic, followed by two short paragraphs and a margin note reading 01, On reading. Generated illustration: the same identity on a tablet in dark mode. Paper-coloured serif type on near-black reads Every line, considered, with considered in italic, above two short paragraphs and the caption Measured Type, Reading specimen.

Generated illustration: a desktop monitor showing a light ops dashboard titled Service health, with three summary cards and a six-row table whose request and error counts are right-aligned so their digits line up. Every status is a word; Scheduler reads Down in red.

Generated illustrations of the identity. They show no measurement.

Support Measured Type

Measured Type app icon: a paper-coloured lowercase m above a measure rule on a rounded red tile.

Measured Type is free to use. If it saves you time, optional sponsorship helps
keep it measured and maintained. Issues with a reproducible case, new eval
cases and corrections to the references are welcome contributions too.

Sponsor on GitHub
Buy Me a Coffee

License

Text (SKILL.md, references and eval prose) is CC BY-NC 4.0 because it adapts
Richard Rutter's webtypography.net, which is
published under that license. Scripts are MIT. The test fonts are under the
SIL Open Font License. The Measured Type name and the artwork in
.github/brand/ identify this project and are not licensed for other use.
See LICENSE; the skill folder carries its own copy of the notices,
so an installed skill keeps them.

Can I use it in paid work? The scripts, MIT, can be used anywhere. The
text is CC BY-NC 4.0, which restricts uses primarily intended for commercial
advantage. Whether an agent consulting it during paid client work is such a
use has not been settled with the rights holders; this README does not
settle it. If it matters for your work, read the license or ask before you
rely on it.

The rules follow Robert Bringhurst's
The Elements of Typographic Style and
the rule order of webtypography.net. They are paraphrased and the CSS is
rewritten for current browsers. The skill is a working translation, not a
substitute: if it is useful to you, buy the book. The wordmark is set in
Source Serif 4 by Adobe; the brand's interface type is IBM Plex. Both are
released under the OFL.

Measured Type is an independent project. It is not affiliated with, endorsed
by or sponsored by Robert Bringhurst, Richard Rutter, webtypography.net, Matej
Latin, Ina Saltz or the publishers of their books; their names appear only to
credit the sources.

Reviews (0)

No results found