Tokens
Defined once in TypeScript. The JSON, the CSS custom properties, and the StyleX vars are generated.
The neutral scale
Ink to paper. There is no accent.
Colour
| Token | Light | Dark |
|---|---|---|
paper | #FAF8F2 | #0E0C0A |
ink | #1A1A1A | #E8E8E8 |
gray | #6B6B6B | #949494 |
divider | rgba(0,0,0,0.08) | rgba(255,255,255,0.10) |
dividerSubtle | rgba(0,0,0,0.06) | rgba(255,255,255,0.07) |
gridLine | rgba(0,0,0,0.04) | rgba(255,255,255,0.05) |
tableAlt | rgba(0,0,0,0.02) | rgba(255,255,255,0.03) |
controlBorder | rgba(0,0,0,0.42) | rgba(255,255,255,0.38) |
Two grid inks in one family: divider is the stronger step for cell cages and rules; gridLine is the quieter step for the 204 verticals. controlBorder is the one boundary that must reach 3:1, so form controls read on paper and on black. Each scheme also sets color-scheme. The custom property names and the override recipe are in Theming.
Type scale
| Style | Size | Weight | Tracking | Leading |
|---|---|---|---|---|
| displayXl | 52px | 600 | -0.035em | 1.05 |
| display | 38px | 600 | -0.03em | 1.15 |
| title | 22px | 600 | -0.02em | 1.3 |
| subhead | 17px | 500 | -0.01em | 1.5 |
| body | 15px | 500 | -0.01em | 1.6 |
| label | 13px | 600 | -0.01em | 1.3 |
Inter, 500 for body and 600 for headings and labels. Sizes ship in rem with no root font-size pin. The measure is 3 columns, 592px, about 66 characters. Never all caps; labels and eyebrows are sentence case. Reading type multiplies by --text-scale (steps 0.9, 1, 1.1, 1.25, 1.4); chrome stays put.
The grid
204px modules: a 184px column and a 20px gutter. Inner pages draw the quiet --grid-line verticals, including the left gutter. Home and About use a stronger --divider cell cage, with --grid-line only on the left and right of the box. Content boxes span whole modules; edges step from grid line to grid line on resize. Up to 6 modules, 1244px.
| Breakpoint | Width | Changes |
|---|---|---|
mobileGrid | 480px | Two columns on 25px gutters; --pad 25px |
mobileLayout | 640px | Phone control scale, stacked layouts |
rail | 1024px | Docs rail and the one-module inset |
wide | 1440px | The airy first module returns on the catalogue |
cap | 1700px | Layout stops growing |
Radius
One token: --radius-sm (4px), the standalone button radius. Toggles share it. Cards are chrome-square (no frame, 0). Callouts are hairline, radius 0. Nested inners follow Steve Ruiz. Chrome stays 0. Chrome stays 0. Nested corners stay concentric: Steve Ruiz innerRadius, clamped at 0. The rule lives in the kit; it is not a catalogue card.
import { innerRadius, concentricInner, concentricOuter } from "@noorddev/vlak";
innerRadius(28, 16); // 12
concentricInner(28, 16); // 12
concentricOuter(12, 16); // 28
// CSS: --rs-out and --rs-gap; --rs-in is the closed form of the fit.Control scale
Desktop: 40px controls, 14px type, 12px labels, 40px hits. At or under 640px: 44px controls, 16px type, 15px labels, 44pt hits. The desktop is the poster; the phone is the hand.
Motion
A state the user caused may ease, snap with a short curve, or confirm. Entry is not a show. Color and opacity name the change; nothing bounces. Snap 0.12s, ease 0.2s, confirm 0.16s. Curve cubic-bezier(0.3, 0, 0.2, 1). Under prefers-reduced-motion: looping demos and unsolicited entry disabled under prefers-reduced-motion.
Icons
16 viewBox, 1px currentColor, drawn at 12 or 16. 1px currentColor hairline, butt/miter, no rx, no fill; draw at 12 or 16.
Programmatic access
import { vlakTokens } from "@noorddev/vlak";
vlakTokens.color.light.paper; // "#FAF8F2"
vlakTokens.color.light.controlBorder; // "rgba(0,0,0,0.42)"
vlakTokens.grid.module; // 204
// as JSON
import tokens from "@noorddev/vlak/tokens";
// in StyleX
import { vlak } from "@noorddev/vlak-react/tokens.stylex";
// in the terminal
// npx @noorddev/vlak-cli tokens