@n8mills/design-tokens
v1.1.0
Published
The design tokens behind natemills.me: DTCG 2025.10 source, generated CSS custom properties, the component layer, and a resolved JS/TS export. Three-tier, OKLCH-authored, WCAG 2.2 AA.
Maintainers
Readme
This is the real design system behind my portfolio, opened up so you can see how it is built, not just that it exists. It is small on purpose: one accent, a tuned neutral scale, two fonts, and a strict three-tier token model. Everything starts in one tokens.json file and propagates outward, so reskinning the brand is a one-line change and every component follows.
I publish it because the discipline is the point. A design system is easy to claim and hard to govern. This repo is the audit trail for the claims: the token source, the generated CSS, the component layer, and the written rules that keep them honest.
See it live. The under-the-hood design system resolves every token in real time, in light and dark, section by section. This repo is the source it runs on.
What's inside
One source, three generated outputs, and the discipline between them. That is the whole system.
tokens.json: the single source of truth, in the W3C Design Tokens Community Group (DTCG) format. Three tiers: primitives (raw values), semantics (roles), and component tokens (btn/card/chip/badge, aliasing semantics only).tokens.css: the generated CSS custom properties, built fromtokens.jsonby Style Dictionary. All three themes (light, dark, high-contrast) included. Never hand-edited.tokens.js/tokens.d.ts: the same tokens as a typed JavaScript object, resolved to literal values for the default theme. For build tools that read tokens in JS, not CSS. Generated, never hand-edited.components.css: the component layer. Buttons, cards, chips, and the section heading pattern, each reading semantic tokens only.design-system.md: the full write-up. Philosophy, the token model, the type and spacing scales, accessibility, contribution rules, and the content voice guide.DESIGN.md: the portable brand contract. One self-contained file that hands an AI coding agent (or a contractor) everything it needs to build on-brand without the repo: resolved colour, type, spacing and radius values in the frontmatter, a component recipe map, and the do's and don'ts. The value blocks are generated fromtokens.jsonand drift-gated, so they never fall out of sync with the system.
Prefer the guided tour to the raw source? The live view walks every section with the real values resolved.
The model in one breath
PRIMITIVES raw values, named by hue and stop --> e.g. --neutral-850: #1c1c1f
SEMANTICS roles, reference primitives via var() --> e.g. --color-accent: var(--neutral-850)
COMPONENTS classes that read semantics only --> e.g. .btn--primary { background: var(--brand-lime) }One way, top to bottom: components read semantics, semantics read primitives, primitives are literals. Nothing reaches back up. So one edit at the top, a new brand hue, propagates through every semantic and component that uses it. No find-and-replace, no drift. That propagation is the whole point.
The foundations at a glance
Colour, type, motion, and spacing, all generated from tokens.json. The type is Inter for everything, JetBrains Mono for labels and numbers, and PT Serif for the one place that earns it: the pull quotes.
Accessibility is a constraint, not a feature
It is wired into the tokens, so you cannot opt out of it by accident:
- Every text token is contrast-checked for WCAG 2.2 AA: a blocking CI lint over the token source in both themes, plus live ratio computation on the Under-the-Hood page.
- Focus rings are never removed without a replacement.
- Default control height is 44 by 44 px. Two documented dense sizes sit below it on purpose, a 32px small button and a 24px dot; both clear the WCAG 2.5.8 AA 24px floor and carry a larger pointer target than their painted box.
- Every animation is gated behind
prefers-reduced-motion, collapsed in one place, not per component.
There is one honest carveout. The brand lime is a background and accent only. It fails as foreground text on the light surface, roughly 1.06 to 1, so the system refuses to let you use it there and hands you a theme-aware token instead. The full reasoning is in design-system.md.
Use it
Install from npm:
npm install @n8mills/design-tokensPull in the CSS custom properties (light, dark, and high-contrast themes all included), through your bundler:
@import "@n8mills/design-tokens/css";
@import "@n8mills/design-tokens/components.css"; /* optional component layer */
.cta {
background: var(--brand-lime);
color: var(--brand-ink);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-5);
}Or read the resolved values in JavaScript or TypeScript (the default theme, fully typed):
import { tokens } from "@n8mills/design-tokens";
tokens["brand-lime"]; // "#d2ff37"
tokens["color-text-primary"]; // "#0a0a0b"
tokens["space-5"]; // "24px"The raw DTCG source ships too, for your own token pipeline:
import dtcg from "@n8mills/design-tokens/tokens.json" with { type: "json" };Prefer the source directly? Clone it:
git clone https://github.com/n8mills-UI/nate-mills-design-system.gitRead design-system.md for the why. Read tokens.json and components.css for the how. Fork it for another brand by changing --green-500 and watching every consumer follow. That is the system working as designed.
License
MIT. Use it, learn from it, build on it.
Built by Nate Mills, Design Systems and Full-Stack Design. natemills.me | LinkedIn
