The locked-up type every Plausible Venture is printed from.
A forme is the assembled type and blocks, clamped in a chase, that a press takes its impression from. Every sheet off it is the same shape; what changes is the ink. That is the arrangement here: this package is the shape, and a venture supplies one colour and its own content.
Extracted from Punchcutter and Foley, which arrived at the same design independently and then had to be diffed to find out what was actually shared.
npm i github:plausibleventures/formeimport '@plausibleventures/forme/forme.css';
import { $, el, createLens, fitCanvas, readVar } from '@plausibleventures/forme';Then set your one colour, and nothing else about colour:
:root {
--spot: #1d6b5b;
--spot-lit: #2f9d83;
--spot-wash: rgba(29, 107, 91, 0.09);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) { --spot: #4fbfa3; --spot-lit: #7fd9c2; --spot-wash: rgba(79, 191, 163, 0.11); }
}
:root[data-theme='dark'] { --spot: #4fbfa3; --spot-lit: #7fd9c2; --spot-wash: rgba(79, 191, 163, 0.11); }Open proof.html to see every primitive in both themes. It is the fastest way to find out what
exists, and it is also the test — if something looks wrong there, it is wrong.
Paper and ink, not a UI kit. One near-white, one near-black, four greys between. No elevation levels, no surface tints, no shadows — depth is a hairline or it is nothing. A page built from this reads as a printed document rather than a stack of cards, and printed documents are what people trust with dense information.
One spot colour, and it means one thing. It marks the live value: the thing being made, the number that just moved, the control under the pointer. Never decoration, never a heading, never a button that does not act. The moment a second meaning is put on it, the page stops being readable at a glance — which is the only advantage this palette has over a normal one.
Everything is mono, and small. --mono for reading, --tech for labels. tabular-nums is set
on body rather than sprinkled about, because these pages are mostly numbers and a column of
figures that does not line up is a column you cannot compare down. Body text sits around 0.79rem —
smaller than a web app, about right for a specimen sheet.
The work takes the width; the numbers live in a rail. .sheet is that split and it is the most
load-bearing thing here. The thing being made is never in a modal, never behind a tab, never smaller
than the controls that change it.
The interface explains itself. .note, .axis__note and .lens exist because a control whose
effect you cannot predict is a control you move at random. On these ventures the prose is not
bolted to the bottom as an afterthought; it is half of what people come back for.
Pick one that belongs to the subject rather than to a palette. Verdigris for a type foundry, because that is what a copper matrix goes when it oxidises. Ferric oxide for a sound foundry, because that is the coating on magnetic tape. A colour with a reason survives the first person who asks why it is that colour.
Then check it twice: it has to clear 4.5:1 against --paper in light and against --paper in
dark, and those are two different colours. A spot that works in one and fails in the other is
the single most common way these pages break.
| Layout | .sheet .work .console .block .panel .eyebrow .eyebrow__hint |
| Masthead | .bar .bar__name .bar__what .bar__spacer .rooms .state .hush |
| Stage | .stage .stage__id .stage__name .stage__tag .stage__canvas .stage__foot .readout |
| Buttons | .solid .ghost .ghost--wide .ghost--tiny .icon .row |
| Choices | .picks .pick .pick__name .pick__tag |
| Controls | input[type=range] .axis .axis__head .axis__label .axis__value .axis__ends .axis__note .lens .mini |
| Fields | .field .field__label .field__input .toggle |
| Words | .note .prose .rules .status .check .code |
| Utilities | .hidden .scroller .skip |
| JavaScript | $ el clear debounce fitCanvas readVar createLens download copy |
Every rule carries a comment saying why it is the way it is. Read the CSS; it is the documentation.
.readout sets min-width: 0. A flex item defaults to a min-width of its own max-content, so a
long readout pushes the whole document wider than the screen and every phone gets a page that slides
sideways. This shipped once and took a while to find.
.state needs a min-width if its label changes. A chip that resizes on the first press shifts
whatever sits beside it out from under the pointer that pressed it, and the click lands somewhere
else. That looks exactly like nothing happening.
.skip is clipped, not parked at -9999px. An off-screen box is still a box, and one more
thing that can widen the document.
fitCanvas goes at the top of every draw, not once at boot. A canvas measured while its panel
was hidden has a client width of zero, so the first draw after a tab switch silently produces
nothing.
readVar is called inside the draw, not cached. These tokens change when the visitor switches
their system between light and dark, and a cached colour is a canvas that stops matching the page
around it.
The three-block theme pattern. The prefers-color-scheme rule is guarded with
:not([data-theme='light']) and the [data-theme='dark'] values are repeated. Without the guard, a
venture's "always light" switch is silently ignored by anybody whose system is dark.
forme.js is JavaScript with a hand-written forme.d.ts beside it. The alternative is a build
step, and a build step on a dependency installed straight from a git remote is a thing that breaks
on somebody else's machine six months from now for reasons nobody will enjoy finding. A hundred and
fifty lines do not need a compiler.
Nothing in it touches the DOM at import time, so it is safe to import in a test.
SKILL.md in this repo is the Claude Code skill that goes with the package — the house style, the
shape of the page, the writing voice, the gate that runs before deploy, and the traps that have
already cost a session. It is the half of this that carries judgment rather than CSS.
Install it into the ventures working directory so Claude picks it up:
mkdir -p .claude/skills/forme
ln -s ../../../forme/SKILL.md .claude/skills/forme/SKILL.mdA symlink rather than a copy, so there is one source of truth and it is the one under version control. Skills are discovered when a session starts, so a new one needs a fresh session.
MIT.