Every token, component and rule in the v2 system, shown at working size. Click any swatch to copy its hex.
The stylesheet inlined here is system.css verbatim, copied at build time. Every
swatch fill, contrast ratio and OKLCH reading is computed from one hex value by
build.py, so a label cannot drift from the color above it. If the system changes,
re-run the build rather than editing this file.
Not to look good. To be unable to have been installed.
An audit in August 2026 found that 13 of
13 accent and text values in the previous system were Tailwind's
literal defaults, and that our own product site had been initialised with shadcn
("style": "new-york"). A competitor in an unrelated industry shared
9 of our 12 signature devices at
near-identical values, because both sites were assembled from the same registry.
Every value must trace to a source. If the only answer to “why that number?” is “it looked right,” it is drift.
That test replaced the older one, “could this have been installed?”, because blacklists rot on roughly a twelve-month cycle. The 2024 tell was a purple gradient and Inter. The 2026 tell is warm cream and a serif display, which was the escape route from the 2024 tell, adopted widely enough to become the new one. Derivation does not rot.
Source: design-system.md §0, August 2026 audit.
Never write a prohibition without a positive replacement. Told “don't use cream,” a model does not produce variety, it moves to a different fixed palette. Negation relocates an attractor rather than dissolving one. Every rule in this guide names the token, number or face used instead.
The wordmark's PAY blue is the seed. Every other blue is that same color made lighter or darker.
Step 50 is the wordmark blue itself, measured at OKLCH hue 245.56. Across all eleven steps the hue holds within 1.6 degrees. Chroma is a different story: it peaks at the seed and falls toward both ends, because sRGB cannot hold saturation at the pale end. The ladder is a lightness ladder with the hue locked, which is the property that matters.
Contrast measured against --paper in each theme.
Green clears 4.5:1, gray clears 3:1, red clears neither. Computed 13 August 2026.
#33A9FF measures 2.42:1 on the light ground. On a light
surface it is a fill only. Never text, never a hairline you need to read. On
the dark ground the same hex reaches 7.51:1 and can be set as text. That asymmetry is the
entire reason a ladder exists instead of a single hex.
This text is #33A9FF on paper. At 2.42:1 it fails every accessibility floor there is.
Never do thisThe same hex as a fill behind white, and as the seed of the whole ladder.
This is its jobBecause of that, no one ever hand-picks a step. Six role tokens resolve to the right rung per theme, and the markup only ever names the role.
| Token | Job | Light | Ratio | Dark | Ratio |
|---|---|---|---|---|---|
--mark |
links, emphasis | step 70 #0077bd |
4.57 | step 30 #81c5ff |
10.34 |
--mark-deep |
heavier emphasis | step 80 #005990 |
7.07 | step 20 #b0daff |
13.02 |
--mark-solid |
FILL on light, text on dark | step 50 #33a9ff |
2.42 | step 50 #33a9ff |
7.51 |
--mark-tint |
tinted surfaces | step 05 #edf6ff |
1.04 | step 95 #002e4d |
1.36 |
--fig |
display figures, >=24px only | step 60 #0089d9 |
3.59 | step 40 #5fb7ff |
8.83 |
--fig-inline |
figures in running text | step 70 #0077bd |
4.57 | step 30 #81c5ff |
10.34 |
--fig is 3.59:1 on the light ground, so it is legal only above about 24px.
Making it the default color of a general-purpose figure class means every index numeral and
inline stat silently fails AA, which is exactly what happened once. A two-step figure system
must default to the safe step and make the display step opt-in.
Never introduce a second accent hue. Brass was tried for figures and rejected, then survived unnoticed in an atmospheric wash at chroma 0.086 for two drafts. If a color is above roughly 0.02 chroma and is not this blue, it is a bug. Two exemptions exist and both must be made auditable rather than left implicit: third-party brand marks keep their owner's color, tagged so the audit whitelists the attribute and never the hex; and a product replica that shows a user what they actually see on login keeps the product's own tokens, asserted in both directions.
Both themes are warm. The accent is the only cool thing on the page.
The blue sits at hue 245.56, so its exact complement is 65.56. The neutrals are derived from that number, which means one measurement seeds both halves of the system and the answer to “why that hue” is arithmetic rather than taste. Chroma and lightness carry each neutral's role.
Measured, the twelve neutrals span 62.35 to 75.41 degrees. That is not drift in the colors. At chroma 0.005 to 0.013, 8-bit sRGB cannot address hue: one step on a single channel moves the measured hue by 3.5 to 15.8 degrees, so every value is already within one quantization step of the target, and re-encoding at exactly 65.56 returns the same or an adjacent hex. The checkable form of the rule is chroma at or below 0.013, hue between 55 and 90. Both hold for all twelve, and both are falsifiable.
Route every border through rgba(var(--rule-c), a). The token is
not a color but an RGB triplet for compositing: 31,27,22 on light,
233,230,225 on dark, at alpha 0.09 to 0.45. Never hardcode a gray, and never a
neutral-black shadow. The light shadow tint is a warm 44,32,21.
--paper-2 and --wash are surface tints only.
--graphite, --mark, --risk and --good all
fall to roughly 4.3:1 on the first and 3.9:1 on the second, which is below the floor. If you
paint a band in either, the text on it steps up to --ink-soft or darker.
There is deliberately no amber warning. Amber sits near hue 70 and collides with the warm ground, which is precisely the mistake the retired brass accent made. Anything that would have been a warning is either a risk or a plain accent note.
Three of these four clear the 4.5:1 floor by 0.04 or less. They have no headroom left. Do not nudge them.
The roles are not a preference. A face doing another face's job is a defect.
Display, section titles, eyebrows, folio, and all figures. Never body, never a UI label.
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 and its italic
Body, UI, card titles, table heads, buttons. Never display, never figures.
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789
Technical strings only: code, IDs, lot numbers, swatch labels. Never a label, an eyebrow, or a running head.
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789
Geist and Geist Mono are retired: they are Vercel's own house typefaces, and wearing the platform's uniform was half the problem. Inter, Roboto, Arial, Poppins, Montserrat, Space Grotesk and any system-font display stack are out for the same reason at a larger scale. They are what a generator reaches for when nobody decided.
It is one of the four most-flagged “AI font” faces, and warm cream plus a serif display is the documented 2026 house style of the frontier models. The defense is not the face, it is the job we give it: figures. A tabular serif figure column in the blue is a device with a population of one. An italic serif word dropped into a headline for flavor is the attractor. Keep the serif's center of gravity on numbers.
Nine steps, and they are the whole vocabulary. Type is judged comparatively, so a slow ratio works here. Space is judged in isolation and gets a faster one.
An oversized serif with one italicised word in it is the universal AI-startup hero, and it is the one place this display face could slide from signature into flourish. The thing that makes it a tell is that it is usually a default, reached for once, for flavor, with no reason behind it.
An italic serif word dropped into a headline because it looked good there. One-off, unexplainable, decorative.
Not a systemAn accent device that is traceable to a stated source, applied systematically across a surface, and carried in the content model as data rather than hand-marked per instance.
A systemIn documents there is no such system, so four sanctioned italic roles are the whole
vocabulary, and there is no fifth: .doc-title em (a whole title line, never
a word lifted out), .tag, .note-l, and .quote. Emphasis
inside a document heading comes from size, weight, color or position. Gradient fill on text is
banned everywhere, including on a sanctioned accent word: the italic already marks it, and the
ramp only adds a tell.
The eyebrow is Instrument Serif italic, sentence case, graphite, with a 26px hairline prefix. It carries the section's kind; the numeral carries its position. If an eyebrow would only restate the title, leave it out. The old mono-uppercase-tracked eyebrow is dead and must not return in any form.
.label — Archivo, --t-xs, weight
600, tracking .09em, uppercase, graphite. This is the table th treatment
promoted to a class, and it is the only sanctioned uppercase-tracked device left. Use it
for a column heading.
.tag — serif italic, sentence case, graphite,
where the text is a phrase rather than a field name. Reach for this one
first.
One device with a population of one. It cannot be pattern-matched, because nobody else does it.
Every number is set in the display serif, in the blue, with
font-variant-numeric: tabular-nums lining-nums, right-aligned wherever it sits in a
column. In running text .fig-inline runs slightly larger than the surrounding
Archivo so it catches the eye without going bold.
Without the treatment: we recovered 20 million dollars across 450 holds for 2,500 businesses, working with 30 processors.
With it: we recovered $20M across 450 holds for 2,500 businesses, working with 30 processors.
Tabular figures are what make a column readable: every digit occupies the same width, so the numbers line up vertically whatever their value. Right-alignment does the rest.
Same class as a missing logo. A number in a stat card is a claim, so it carries a real value, a denominator where one exists, a date, and where it came from. Round unsourced numbers — “10,000+ merchants”, “99.9% uptime”, “3x faster” — are the most-cited content tell there is. Placeholder figures may exist in a scaffold. They may never ship.
Body is 17px on a 30px baseline, so one line is one unit.
Every spacing value is a fraction or a multiple of that line, and every one is a whole pixel. A geometric ratio was considered and rejected: 1.25 needs fourteen steps to span 4 to 100px and puts 24 next to 30, which reads as an accident, and 1.5 goes fractional at the third step.
The blue rules behind this paragraph are drawn every 30 pixels. The text sits on them
because --lh-body is 1.765, and 17 × 1.765 is 30.005. One line of body copy
is exactly one unit of the spacing scale, which is why the scale can be stated as fractions of
a line rather than as an arbitrary set of numbers somebody liked.
Space between text blocks uses whole baselines: 30, 60, 90. Padding inside a component uses the fractions: 6 through 24. Proximity carries meaning, so make the gaps differ where the grouping differs. One spacing value everywhere is a failure to group.
A two-step shape scale, one hairline weight, and exactly two atmospheric elements.
A control that sits a step tighter than its container reads as considered rather than defaulted. One radius on everything is the soft-blob tell, and the fix costs nothing.
--r-lg--r-ctl--r-xsAll four corners, always. Elliptical, chamfered, squircle, scaled
and asymmetric-keyed corners were each built and rejected, and they stay rejected.
--r-sm is retired. Never reach for a raw px value: the two tokens are the whole
vocabulary.
--hair is 1px everywhere, colored
rgba(var(--rule-c), .09 to .45). There is no second border
thickness in the system.
A translucent card over the warm ground, with
backdrop-filter: blur(16px) saturate(1.25). Held rather than dropped, because
grain over it and halos behind it make it a material stack, not a blur applied for gloss.
Including over the cards, at z-index 9. Underneath, every card would erase it and the two would visibly fight. Over the top it behaves like film grain and unifies the surface. One grained surface per flat page, and never one grained box nested inside another.
That is the entire atmospheric budget. Not a third halo, not an aurora field, not a mesh gradient, and not a glow orb of any edge treatment. “Blurred” does not launder one.
Grain must ride the layer that paints the tone, never the section box. In a bridge or bleed system a band's color continues past its own section, so grain scoped to the section ends while its color carries on, and draws a hard line at every boundary: measured at Δ10/255 on paper and Δ22/255 on the dark ground. The page then reads as a stack of slabs rather than one surface.
The card is the house container. It is not the house default.
A card says “compare me to my siblings.”
Use one when the items genuinely are a set of peers the reader weighs against each other: three plans, three approaches, three outcomes. Anything the reader simply reads in order goes on the ground with no container, as a title, a line, and a hairline rule between rows. A page where every block is a card grid reads as noise no matter how good the cards are. On one four-route service section, applying this took the page from about 24 card surfaces to 9.
The same applies to proof imagery and pull quotes: a screenshot that already ships its own frame and shadow needs no card around it, and a quote reads better as type on a rule than boxed. The four blocks in section 07 above are card-free rows, for exactly this reason.
Label left, figure right. The ledger row, boxed.
64chA tag, a title, a line of copy, and a link whose arrow travels with the card.
Back to the top →Hover any card above. At rest it carries a hairline and no shadow. Elevation arrives only on interaction, and it arrives in exactly two layers.
The card rises 3px and gains --shadow-lift from nothing. It becomes
physically nearer.
A soft highlight tracks the pointer across the surface, at alpha .035.
Expensive materials catch light. This is the layer that sells the material.
It shipped at .14 and read as a spotlight following the cursor rather than a
material catching light. The tell that it is right: you notice it when the pointer moves and
cannot quite point at it when it stops. Every surface shares one block, so this single number
tunes the whole page. Never re-tune it per component.
A third layer, a blue rule wiping across the top edge, was built and removed. With the sheen already carrying the accent it was one signal too many, and it made the card announce itself rather than respond. Shipping a hairline and a diffuse shadow at rest was dropped for the same class of reason: it is running two elevation systems at once.
A ledger never closes a column with a single line. .total-line draws two
hairlines 4px apart above a total. It costs one pseudo-element, it is nobody else's, and it means
something. Use it only above a genuine total, never as decoration.
.n to right-align and .num to set
the figure in the serif.| Measure | Before v2 | After |
|---|---|---|
| Tailwind default values | 13 | 0 |
| Vercel typefaces | 2 | 0 |
| Accent hues | 2 | 1 |
| Radius steps | 1 | 2 |
| Reveal durations | 1 | 2 |
Source: design-system.md changelog, 3 August and 12 August 2026.
A card inside a card is visual noise. If content inside a card needs a frame, it wants a rule or a tinted band, not a second card.
Everything arriving identically is decoration. Varying it by importance makes motion carry meaning.
| Class | Travel | Duration | Use on |
|---|---|---|---|
.rise | 18 | 0.7 | Section heads, anything that opens a block |
.rise-sub | 10 | 0.5 | Cards, grids, tables inside a section |
| none | — | — | Points, labels, inline elements, small controls |
The lead announces the block; the follower settles under it. Do not add a third duration, and do not put a reveal on something small. An element that arrives on its own for no reason is the same failure as everything arriving identically.
One pair, and no bounce or elastic anywhere. Every hand-picked easing curve in the measured corpus is an ease-out, which is what both of these are.
Color, background and border transitions.
Reveals, card lift, the travelling arrow.
The measured corpus norm for a hover is a color change: background 33.8%,
color 24.4%, border 12.3%, transform only 6.6%. Our two-layer lift is correct on a card and
wrong on a link or a table row, which want color at about 200ms. Per-site median transition is
250ms, so --dur-lead at 0.7s and --dur-sub at 0.5s are top-decile
values: right for reveals, wrong anywhere near a hover.
Source: motion-canon.md, 1,199 sites' shipped CSS, 13 August 2026.
transform and opacity only. Never width,
height, padding or margin. Layout properties are 5.7% of
all transition declarations in the corpus, so this one is confirmed rather than asserted.IntersectionObserver, or a hidden tab reveals everything immediately.4.5:1 for text, 3:1 for display figures over about 24px. Every value below was computed from the shipped hex, not copied from a note.
| Token | Job | Light | Dark | Floor |
|---|---|---|---|---|
--ink |
headings | 16.31 | 17.74 | 4.5 |
--ink-soft |
body | 7.93 | 15.38 | 4.5 |
--graphite |
muted text, eyebrow | 4.66 | 6.12 | 4.5 |
--mark |
links, emphasis | 4.57 | 10.34 | 4.5 |
--fig-inline |
figures in text | 4.57 | 10.34 | 4.5 |
--fig |
display figures >=24px | 3.59 | 8.83 | 3.0 |
--mark-solid fill only |
FILL on light; text on dark | 2.42 | 7.51 | 4.5 |
--risk |
error states | 4.54 | 4.53 | 4.5 |
--good |
confirmations | 4.54 | 5.16 | 4.5 |
Ratios against --paper in each theme. WCAG 2.1 relative luminance,
computed 13 August 2026. The system's shipped floors are 3.59:1 on light and 4.53:1 on dark.
--fig at 3.59:1 is legal only above about 24px. Every shipped use qualifies:
the stat figure at 51.9px, the section numeral, row figure and total figure at 33.2px.:focus-visible rings, 2px in --mark, offset 3px. Tab
through this page to see them..card-link is not.prefers-reduced-motion honored, and the theme is applied before first
paint.78.6% of measured sites write outline:none at least once, and 26.5% do so while
never defining :focus-visible. 12.7% have no focus styling at all. Reduced motion
is worse: only 39.8% of sites carry the query, and by platform it collapses to 14.4% on Webflow
and 2.3% on Framer, two sites out of 87. Any Compaytence surface built on
Framer or Webflow needs that query written by hand.
Source: motion-canon.md, 13 August 2026.
In a hidden preview tab, getComputedStyle returns stale values for
anything mid-transition, and forcing reflow does not help. Before measuring contrast, inject
*,*::before,*::after{transition:none!important}. Otherwise a theme flip reports
the previous theme's colors and every reading is wrong.
On 13 August 2026, 1,290 shipped design systems were machine-measured from applied CSS. The numbers below are what came back.
Source: styles.refero.design, 1,290 systems measured from applied CSS; anti-patterns.md §9 and website-builder/references/design-canon.md, 13 August 2026.
Two more that sharpened the system rather than confirming it. Bold is a bigger tell than radius: 46.2% of these sites never exceed weight 600, and 36.6% state an anti-bold rule. Hierarchy is size, space and color, so a 700-weight heading is a more reliable marker of an unconsidered system than any palette choice. And negative tracking is not hygiene: roughly half the corpus applies none, even on display. Track by size band with a stated reason, or not at all.
Never let a warm accent onto the warm paper. Not in an illustration, not in
a chart series, not in a hover tint, not in an atmospheric wash. It is the one move that
collapses a 2.6% position into a 77.3% one, and it is exactly the failure mode already on
record: brass survived unnoticed in a wash at chroma 0.086 for two drafts.
--risk is the sanctioned exception, scoped to error states only.
Most of these tells were 2022 to 2024 human design trends with named exemplars before the generators were any good. What changed is not the vocabulary, it is that the average became free to produce. The real diagnosis is “nobody decided,” not “a machine made it”: a human shipping stock shadcn produces the identical artifact. Point this list at our own work.
Permanently out. If one appears, it is a regression, not an option.
rgba(var(--rule-c), a)box-shadow instead of --shadow or --shadow-liftsrc<div> with a click handler where a <button> belongsThe retired v1 system is archived and must never be rendered: no
cream #fafaf7 canvas, no Geist, no #3b82f6, no mono eyebrows, no glow
orbs, no grid overlays, no dark navigation rail.
Six questions. Any “no” is a defect, not a preference.
Replace the wordmark with a competitor's. Does anything else have to change?
Pick five rendered values at random: a color, a radius, a gap, a size, a duration. Can each be traced to a token or a stated derivation without saying “it looked right”?
Does the page show what matters most, or is every block the same weight? Identical word counts across sibling cards mean the copy was trimmed to fill the boxes.
Does every figure and quote carry a real source, a date and an attribution?
Do the empty, loading, error and 404 states exist, and does each say something specific?
Does it hold up scrolled to the bottom at 375px and at 1440px, in both themes? A page verified only from its first viewport is verified the way a generator would verify it.
If the build is being matched against a reference site, add a seventh: put our page, the reference, and the closest generic template side by side. Distinctiveness is only visible in a lineup.
This page is the human one. The gradeable version, with every rule carrying an ID, a
measured value, a test and a source line, is
.claude/skills/compaytence/references/design-system-spec.md. It is what the
design-loop skill's system critic resolves to, and its §15 is a pre-built
twenty-line checklist ready to fork into a run.