PetriLahdelma/measured-type
PetriLahdelma/measured-type has 18 GitHub stars and 2 forks. GitHot measured 0 stars in 24 hours and 0 in 7 days. Last checked 2026-11-13 01:34 UTC. Last push 2026-11-03.
Agent skill for web typography: Bringhurst applied to modern CSS, with claims measured in the browser and a baseline eval suite
Measured star growth
- in 24h
- 0
- in 7 days
- 0
- in 15 days
- Collecting history
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalInstall Where it helps Evidence Read the skill Contribute 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.
| 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:
66chin 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 --fitgives 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 installand 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.
Installed in a project, it loaded before the agent's first edit on every typography prompt with Opus 5.5 (26 of 26) and on 15 of 16 with Sonnet 5.5, and never on a near miss (spec 0016). Other type or design skills installed alongside it were not tested; name it when you want it for certain.
How it works
- Classify the text. Reading, display and interface text follow different rules; book rules applied to a button are their own mistake.
- Direction and language first. Every rule is flow-relative, with
langanddirin place, so an RTL or vertical edition is a content change. - 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. - 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.
- 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).
Claude Code plugin, from this repository's marketplace:
/plugin marketplace add PetriLahdelma/measured-type
/plugin install measured-type@measured-type
The plugin installs the skill and its scripts, not the evals, and runs
npm install for Playwright itself. Add the browser with
npx [email protected] install chromium, and fontTools as below.
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.
- Put this article on an 8px baseline grid and check that it holds.
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. --baseline 8px also checks a strict baseline grid: where each block's baselines land, more than 1px off is reported, with the line-height that caused it. The unit can be a custom property the page sets (--baseline --u, so it may change per breakpoint or region), checked at 1280, 768 and 375px, in one region (--baseline-scope) and to any tolerance (--baseline-tolerance). --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:
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:
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:
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 |
| Setting text on a strict baseline grid | Strict baseline grid, at the end of Vertical space in measured intervals, then page-audit.js --baseline |
| 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 |
| Spec 0017 | Held-out line length: 62 characters in Charter at 19px, where 62ch gives 76, Sonnet, 30 runs |
0.18 characters off on average | 0.82 with the hint, 0.43 with nothing | A held-out gain over one sentence (p = 0.004); no detectable difference from a model that measures without the skill (p = 0.077) |
| Spec 0018 | Committed runs re-graded with bands of 2 characters and 5% of each size, after the fact | 20/20 within 2 characters of 66 | 2/10 with nothing and no browser, 1/10 with the hint and no browser | Exploratory: without a browser the skill's estimate is the clear gain; the wider bands hid it |
| Triggering, stub command | 16 typography prompts, 16 near misses, 3 runs each, skill-creator's method | 5 of 48 loads | The skill offered as a stub command in an empty folder, first tool call only: rarely loads | |
| Triggering, real session (spec 0016) | 26 typography prompts, 22 near misses, the skill installed in a small site, Opus and Sonnet, 3 runs each | 78/78 loads with Opus, 44/48 with Sonnet | no near miss loaded in 114 runs | Loads before the first edit on nearly every typography task; the stub-command figure was the setup |
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 |
A strict baseline grid splits the same way. Rhythmguard's
editorial-baseline presets keep spacing values on a 4 or 6 unit cadence in
the source; whether the text then sits on the grid depends on the font that
loads, so page-audit.js --baseline measures it in the browser
(spec 0020).
Identity
Generated illustrations of the identity. They show no measurement.
Contributing
Corrections, wrong results and new eval cases are the most useful
contributions, and each has an issue form that asks for what makes it
checkable: a page that reproduces the result, a source or a measurement.
CONTRIBUTING.md covers the setup (npm test with
Playwright and fontTools), the spec-and-test-first workflow, the house style
and how an eval is pre-registered and reported. Questions and ideas go to
Discussions;
changes are listed in the changelog. Everyone taking part
follows the Code of Conduct, and security problems are
reported privately (SECURITY.md).
Support Measured Type
Measured Type is free to use. If it saves you time, optional sponsorship helps keep it measured and maintained. Contributions are welcome too (Contributing).
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. Contributions take the license
of the files they change (CONTRIBUTING.md). 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.
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
562,251 starspublic-apis/public-apis
A collective list of free APIs
498,658 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
456,585 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,428 stars