A precommit check that keeps a Tailwind codebase on its design tokens. It blocks new arbitrary values (text-[13px], tracking-[-0.02em], leading-[1.1], raw #hex) in components, while leaving the token files alone.
It is a ratchet. It reads only the lines added in the staged diff, so existing debt never blocks a commit, and new debt cannot get in. A moved or reformatted line is not counted as new. One file, no dependencies, Node only.
I wrote it because every project I have worked on drifts the same way: someone needs a 13px label at 11pm, types text-[13px], and six months later there are forty font sizes. This stops the forty-first.
Copy the two files into scripts/ in your repo, then add to package.json:
"scripts": {
"guard": "node scripts/guard.mjs",
"guard:audit": "node scripts/guard.mjs --audit",
"prepare": "node scripts/install-hook.mjs"
}npm install runs prepare, which writes .git/hooks/pre-commit. From then on every commit is checked.
npm run guard # what the hook runs: checks the staged diff
npm run guard:audit # whole-repo count of existing violations, never fails
git commit --no-verify # the escape hatch for a genuine one-offA blocked commit looks like this:
✖ Design-system guard blocked this commit — new arbitrary values:
components/Card.tsx:41 arbitrary font-size — use a named text-* token
<span className="text-[13px] text-slate-500">
1 violation(s). Use a token. Genuine one-off? git commit --no-verify
| Pattern | Message |
|---|---|
text-[Npx|rem|em] |
arbitrary font-size, use a named text-* token |
tracking-[…] |
arbitrary letter-spacing, use a tracking-* token |
leading-[N] |
arbitrary line-height, use a leading-* token |
#hex |
raw hex, use a color token |
Raw hex is allowed in token files: anything matching tokens.css, globals.css, *-system.css, tailwind.config.*, or theme.css|ts|js. Edit TOKEN_FILES and RULES at the top of guard.mjs to fit your setup. It checks .tsx .jsx .ts .js .css .html .vue .svelte.
It is the precommit check on Bench, a shop-floor build-execution prototype, and on two client products. The pattern that goes with it: one token source in globals.css, a named type scale, one button system, and the rule that the third occurrence of a utility cluster becomes an @utility.
MIT
Written by Bryan S. Holland · portfolio · bryan@automaticdelight.com