designesy
Score a site

designesy.org/methodology45 checks14 categoriesengine 1.5.0contract v0.4.6contract JSON

Methodology

How the 45-check engine turns a URL into a score, and how that method is tested. Deterministic: no language model and no human judgment, only regex, token-resolution and spec tests against the HTML and CSS a site serves.

Checks
45
Scored
42
Manual
3
Conditional
1
Categories
14

How a score is made

The engine fetches the page's HTML and every stylesheet it links, parses the :root custom properties, and runs 45 deterministic checks across 14 weighted categories. Each check returns PASS, WARN, FAIL, MANUAL or N/A. The score is a weighted average, then adjusted by an anti-slop deduction, an originality lift and two caps.

It reads the delivered response. The engine reads the HTML the server sends and the stylesheets it links. It runs no JavaScript and waits for no hydration, so on a site that renders in the browser every check reads the markup that arrives over the wire: a heading injected by script is absent to it, and a token set at runtime is not in the CSS it fetched.

That is a deliberate trade. The score stays deterministic, reproducible and cheap, free of headless rendering's timing flakiness: the same input yields the same output, which is what makes a score comparable week to week. It also bounds the claim: a low score on a client-rendered site describes its delivered HTML. Every result carries a receipt with retrieved_at, engine_version and a digest of the verdicts, so anyone can re-run the URL and confirm the answer.

MANUAL and N/A checks are left out of both numerator and denominator, as Lighthouse does with manual audits, so no site is penalised for what the static engine cannot run or what does not apply. The 3 manual checks need a browser viewport, a performance trace or live interaction; the conditional check applies only when a site serves what it reads.

The pipeline, in order

  1. Weigh the checks

    Each scored check carries its category’s weight, split across that category’s scored checks. Pass counts 1, warn 0.5, fail 0.

  2. Subtract slop

    Twelve anti-slop rules take up to 20 points off for recognisable template patterns.

  3. Add originality

    Seven kinds of craft add up to 8 points, halved when the slop is heavy.

  4. Apply the floor

    Accessibility under 60 caps the score at 70, a C.

  5. Apply the ceilings

    Any failing check caps the score at 89.9, a B. Seven severe failures cap it at 65, 70 or 75.

The formula
# Per-check weight: the category weight, split across its scored checks
checkWeight = CATEGORY_WEIGHTS[category] / count(scored checks in category)

# Status scoring
PASS    → 1.0 × checkWeight
WARN    → 0.5 × checkWeight
FAIL    → 0
MANUAL  → excluded (not counted in the total)
N/A     → excluded (not counted in the total)

# Step 1: weighted compliance
weightedScore = round( Σ(weightedPoints) / Σ(weightedTotal) × 1000 ) / 10

# Step 2: anti-slop deduction, 12 rules, up to 20 points
slopTotal = min( Σ(per-rule deductions), 20 )
score = max( 0, weightedScore − slopTotal )

# Step 3: originality lift, up to 8 points (halved when slop ≥ 12)
originalityPoints = min( rawOriginality, 8 )
score = min( 100, score + originalityPoints )

# Step 4: accessibility floor
if (a11yPct < 60) score = min( score, 70 )

# Step 5: hard-fail ceilings, after the lift (it never carries a score past one)
if (any check FAILs) score = min( score, 89.9 )     # no A beside a FAIL
if (v06 | v44 FAIL) score = min( score, 65 )        # contrast, status text
if (v22 | v02 | v16 FAIL) score = min( score, 70 )  # CTA contrast, overflow, rem
if (v24 | v25 FAIL) score = min( score, 75 )        # touch targets, headings

# Per-category sub-score: step 1, scoped to one category
categoryScore = round( Σ(catPoints) / Σ(catWeight) × 1000 ) / 10

round(… × 1000) / 10 keeps one decimal place: 95.2, never 95.2347. The per-category sub-scores use step 1 only; they are the category profiles on the leaderboard.

Category weights

Weights follow the contract's own emphasis. They are relative and sum to 117; the formula normalises them, so a category's real share of the score is its weight over 117, and only over the categories a site can be scored in. Performance and responsive have no scored checks in the static engine, so their weight is held in reserve.

The weight each category carriesHeaviest first. Bars are to scale; the figure under each name is its share of the 117 total.

Weights from lib/check-definitions, which mirrors the engine route.

The numbers
Category weights, their share of 117, and check counts.
CategoryWeightShareChecksScored
Typography1815.4%1313
Accessibility1512.8%77
Color roles1210.3%22
Motion108.5%55
Tokens97.7%22
Copywriting86.8%44
Interface feel86.8%22
Control polish76.0%32
Page basics65.1%22
Interaction65.1%11
Performance65.1%10
Security54.3%11
DESIGN.md43.4%11
Responsive32.6%10
Total117100%4542

Grade bands

The letter is a threshold on the number: F under 60, then a grade every ten points. An A needs 90 and no failing check: any FAIL caps the score at 89.9.

A90 and up
Ships the contract’s primitives and passes nearly every check the engine can run, with none failing. 1 of 30 sites today.
B80 to 89.9
A solid foundation, with a few checks missing or warned. 9 of 30 sites today.
C70 to 79.9
Clear gaps in cadence, motion or accessibility. The accessibility floor caps a site here. 9 of 30 sites today.
D60 to 69.9
Gaps across several categories. 7 of 30 sites today.
Funder 60
Missing the primitives the contract reads: token systems, reduced-motion blocks, font-synthesis rules. 4 of 30 sites today.

The cohort today

All 30 leaderboard sites on the grade scaleMedian 70.0, mean 72.7, from 44.9 to 100.0. 11 of 30 land in D or F.

Weekly run of 2026-10-12.

Every site's score
Composite score of every leaderboard site, weekly run of 2026-10-12.
SiteRankGradeScore
Designesy1A100.0
Atlassian Design System2B89.9
GitHub Primer3B89.9
X4B87.8
Figma5B84.4
Wikipedia6B83.8
Apple7B83.1
IBM Carbon8B82.8
Roast by AI9B81.7
Vercel10B81.1
Designesy AI Studio11C74.5
Awwwards12C72.1
Linear13C70.0
Stripe14C70.0
zeroheight15C70.0
Radix Colors16C70.0
V&A Museum17C70.0
Google Stitch18D68.5
Material 319D66.4
Notion20D65.5
Adobe Spectrum21D65.1
GitHub22D65.0
Vercel Geist23D64.4
Mozaika24D64.3
IBM Plex25F56.8
Pentagram26F56.6
FWA27F54.6
The New York Timesnot rankedC75.8
Desy Guardnot rankedC73.4
CSS Design Awardsnot rankedF44.9

Accessibility floor

If the accessibility category scores under 60, the score is capped at 70, a C, however high the weighted score. Perfect tokens and no accessibility cannot make an A.

The floor is a softer form of the DSAF enterprise-grade rule, which requires 75 in accessibility. Sixty is strict enough to stop the all-tokens, no-access failure and lenient enough that a site passing three of six accessibility checks is not capped. The cap binds only above 70: a score already under 70 is left as it is.

Anti-slop deduction

A site can meet every contract rule and still look generic. A second pass runs 12 anti-slop rules (S1 to S12) against the most recognisable template patterns, and each one found takes points off the weighted score directly. The deduction is flat, so a sparser site cannot game it.

Per rule, at most 5 points; in all, at most 20. A rule deducts min(severity × min(instances, 3), 5): the fourth sighting of the same pattern adds nothing, three is enough to flag it.

The twelve anti-slop rules: pattern, severity and trigger.
RulePatternSeverityTrigger
S1 Overused fontsInter, Roboto, Open Sans, Montserrat, Poppins, Lato, Space Grotesk, Instrument Serif or Geist in font-family51+ match in any font-family
S2 Full-page gradientA multi-color linear-gradient on body or html, or a fixed or inset: 0 overlay (1px hairlines excluded)5A gradient with position: fixed, inset: 0 or 100vw/100vh in the same block
S3 Purple or violet gradientlinear-gradient with any of #615fff, #8e51ff, #4f39f6, #7f22fe, #a855f7, #9333ea, #7c3aed, #6d28d9, #5b21b6, #4c1d9541+ match in a background with a gradient
S4 Gradient textbackground-clip: text over a gradient spanning two or more hue families (45° buckets; var() stops and neutrals ignored)42+ distinct non-neutral hue families in the stops
S5 Default palette hexes3+ of #0f172a, #1e293b, #334155, #615fff, #8e51ff, #4f39f6, #7f22fe, #6366f1, #8b5cf6, #a78bfa, #0d6efd, #007bff53+ hex matches in CSS
S6 Repeated card grid3+ classes matching .(card|panel|tile|feature|item|box|cell|block) on a repeating grid53+ card-like classes and grid-template-columns: repeat(auto-fit|auto-fill|N)
S7 Emoji as iconsEmoji (U+1F300 to 1FAFF, 2600 to 27BF, 1F1E6 to 1F1FF) inside <button> or a CTA link42+ emoji in buttons or CTAs
S8 AI-pill badges“AI-powered”, “Generate”, “Chat with AI”, “Powered by AI”, “Built with AI”, “AI-driven”31+ match in the HTML text
S9 Lorem ipsum“lorem ipsum”, “dolor sit amet”, “consectetur adipiscing”, “sed do eiusmod”, “tempor incididunt”51+ match in the HTML text
S10 Single font familyOne non-generic font-family on the whole page (serif, sans-serif, monospace and system-ui excluded)4Exactly one family name
S11 Marketing buzzwords2+ of the rule's word list (streamline, empower, world-class, enterprise-grade, next-generation, disrupt and similar)32+ in the body text, tags stripped
S12 Placeholder imagesURLs from via.placeholder, placehold.co, placeholder.com, dummyimage, picsum.photos, loremflickr or unsplash.com/random41+ match in the HTML

Originality lift

The counterweight to slop: seven kinds of craft (O1 to O7), each detectable from CSS and HTML alone, add points. A compliant but generic site earns none; a bespoke one is credited. The lift is capped at 8 points, so it nudges the score rather than steering it, and the score is clamped to 100.

The slop gate. With heavy slop (12 points or more), the lift is halved: craft on a heavily templated site is usually the framework's, and the lift is meant for the author's.

The seven originality credits: what is detected and the points it earns.
CreditDetectionPoints
O1 Bespoke easingDistinct cubic-bezier() curves outside the preset set (ease and its variants, Material, Tailwind v4, Bootstrap). A linear() spring with a body of 20+ characters counts as overshoot.1 for 1 to 2 curves · 3 for 3+ · +2 with overshoot · at most 5
O2 Modern layoutHow many of clamp(), container-type or @container, and subgrid are present1 for one · 2 for two or more
O3 Typographic detailfont-feature-settings, font-variant-numeric, hanging-punctuation, text-underline-offset, font-optical-sizing1 for one · 2 for two or more
O4 Tiered reduced motionA prefers-reduced-motion query that is targeted, where a blanket * { animation: none } earns nothing1, when targeted
O5 Motion choreographyScroll-driven animation (animation-timeline, view-timeline, animation-range), view transitions, or named @keyframes1 for 3+ named keyframes · 2 for scroll-driven or view transitions
O6 Bespoke iconographyInline <svg viewBox> or <symbol> elements, placed by hand rather than from an icon font1 for 3+ inline SVGs or 2+ symbols
O7 Semantic tokensRole-named custom properties such as --surface, --ink, --paper, over hue names like --color-blue-500. The shadcn fingerprint (6+ of its default names) scores zero.2 for 4+ · 4 for 8+ · +2 layering · +2 theming · at most 6

Hard-fail ceilings

A page with a failing check is never an A: any FAIL caps the score at 89.9, a B. Some failures cap it lower, whatever else is strong: a site that fails on contrast or overflows its viewport is held to a C or a D. Every cap applies after the originality lift and the accessibility floor, so the lift can carry a score up to a cap but never past it.

The hard-fail ceilings: the failing check, the cap, and why.
CheckCapWhy
any Any failing check89.9A check the page fails cannot stand beside an A, so the grade is a B at most (engine 1.3.0).
v06 Contrast readable65Text many readers cannot read: a failure of basic legibility.
v44 Status text contrast65The words that report errors, warnings and success fall below WCAG contrast (engine 1.3.0).
v22 CTA contrast70The primary call to action is hard to read.
v02 Horizontal overflow70Content is cut off or scrolls sideways on small viewports.
v16 Rem scale70A root font size under 16px sets off iOS Safari’s auto-zoom.
v24 Touch targets75Controls under 44px are hard to use on touch screens.
v25 Heading hierarchy75More than one h1, or skipped levels: the outline is broken.

What it measures

What a site ships
Inline style blocks and linked stylesheets, the :root custom properties, and the served HTML. A design system can document a rich token taxonomy in Storybook and still score low if its public surface exposes none of it at :root; that gap is what the leaderboard shows.
Deterministically
The same URL gives the same score within the 24-hour cache window. When a site changes its CSS, the score changes on the next run.
Conformance, which is narrower than quality
A high score means the site ships the primitives the engine can detect. A site can pass every check and still be ordinary, or fail many and still be excellent. Read the category profile before the letter: it says where a score comes from.

What it skips

4 checks sit outside the score. 3 need a capability the static engine lacks (a rendered browser, a performance trace, live interaction) and are resolved by the full audit; one applies only when a site serves what it reads.

  • v02manualRoutes render without horizontal overflow at 375px, 720px, 860px, 1080px+Requires a browser viewport trace: the engine fetches CSS, and overflow only exists in a rendered layout.
  • v04manualSound toggle flips aria-pressed and applies the audio preferenceRequires live DOM interaction; the engine does not execute JavaScript or click elements.
  • v37conditionalDESIGN.md spec-layer validation (DESIGN.md format lint)N/A if /DESIGN.md is not served at the target origin; no public convention requires it yet.
  • v21manualCore Web Vitals plausible: LCP < 2.5s, INP < 200ms, CLS < 0.1Requires a CDP trace: the engine fetches HTML and CSS, and vitals need a rendered page with timing data.

How the method is tested

Four studies test the method against itself and against an outside instrument. Each figure below is read from the study's own report file, dated, and each report regenerates with one command.

Sensitivity36 perturbations; the WARN credit moves the most grades (15 of 27)

Every leaderboard site's real check statuses are recomputed under 36 changes to the method: weight scaling, the WARN credit, the slop and originality layers, the bands, the floor and ceilings, and each category favoured one at a time.

Uniform weight changes do nothing. Scaling every weight by 10 or 20 per cent in either direction moves no score by more than 0.0 and no rank: the ratio cancels uniform scaling. The judgment calls carry the sensitivity: the WARN credit (15 grades), then the originality lift (9) and the grade bands (8). Favouring any single category by half again moves at most 2 grades and 2.6 points.

Grades that change under each perturbationThe 22 perturbations that change any grade, most first; the other 14 change none.

sensitivity-report.json, generated 2026-10-12 for contract v0.4.5. Left out, the engine could not read them on that run: nytimes.com, getdesy.com, cssdesignawards.com. Regenerate: node scripts/sensitivity-analysis.mjs.

The numbers
All 36 perturbations: grades changed, rank positions moved, largest score change.
PerturbationGrades changedRank movesLargest change
weights −10% (uniform)000.0
weights +10% (uniform)000.0
weights −20% (uniform)000.0
weights +20% (uniform)000.0
WARN credit 0.5 → 0.25152014.4
WARN credit 0.5 → 0.75152519.9
slop deduction +542415.0
originality lift +59195.0
grade bands tightened 2pts (92/82/72/62)800.0
grade bands loosened 2pts (88/78/68/58)300.0
a11y floor 60% → 50%11314.9
a11y floor 60% → 70%3614.4
hard-fail ceilings removed275.0
any-FAIL cap removed (the rule before engine 1.3.0)120.5
INTERACTION: WARN 0.75 × slop +5152525.1
INTERACTION: WARN 0.25 × slop +5152619.1
INTERACTION: WARN 0.75 × a11y floor 70%132614.1
INTERACTION: WARN 0.25 × a11y floor 70%172019.9
INTERACTION: slop +5 × originality +562016.0
INTERACTION: WARN 0.75 × originality +5142419.1
INTERACTION: WARN 0.75 × ceilings removed202321.6
INTERACTION: slop +5 × ceilings removed52415.0
OAT: cadence weight ×1.50102.6
OAT: accessibility weight ×1.52101.9
OAT: semantic weight ×1.5292.1
OAT: motion weight ×1.52131.9
OAT: tokens weight ×1.5021.7
OAT: takt weight ×1.5081.3
OAT: poise weight ×1.5080.9
OAT: identity weight ×1.5020.9
OAT: interaction weight ×1.50122.6
OAT: performance weight ×1.5000.0
OAT: responsive weight ×1.5000.0
OAT: security weight ×1.5051.3
OAT: spec weight ×1.5021.4
OAT: copywriting weight ×1.51122.0
Version diffv0.3.0 to v0.4.0: 14 of 27 grades moved, mean change -1.6

A method release should never be a silent regression. Each contract version is diffed: the same check statuses scored under the previous version's profile, rebuilt from the changelog, and the change reported per site. From v0.3.0 to v0.4.0, 14 grades moved (6 up, 8 down); the largest rise was apple.com at +6.9, the largest fall pentagram.com at -15.7.

Every score response pins its contractVersion. Before adopting a new version in CI, run the diff: if a grade moves, it says why.

Score under v0.3.0 and v0.4.0 for each site, generated 2026-09-28.
Sitev0.3.0v0.4.0Change
designesy.org100.0 A93.0 A−7.0
atlassian.design79.6 C86.1 B+6.5
x.com79.2 C84.2 B+5.0
primer.style82.3 B82.4 B+0.1
apple.com76.2 C83.1 B+6.9
vercel.com86.2 B81.7 B−4.5
wikipedia.org76.9 C80.7 B+3.8
figma.com81.1 B76.6 C−4.5
designesy.ai.studio70.2 C74.8 C+4.6
awwwards.com66.0 D71.6 C+5.6
roastbyai.com80.7 B71.2 C−9.5
zeroheight.com70.0 C70.0 C0.0
vam.ac.uk70.0 C70.0 C0.0
linear.app70.0 C69.5 D−0.5
github.com81.0 B68.5 D−12.5
m3.material.io61.4 D67.6 D+6.2
stripe.com70.0 C66.9 D−3.1
notion.so70.0 C65.0 D−5.0
radix-ui.com68.8 D63.2 D−5.6
mozaika.design59.2 F62.8 D+3.6
geist.dev62.0 D62.0 D0.0
stitch.withgoogle.com62.0 D61.2 D−0.8
carbondesignsystem.com71.2 C60.7 D−10.5
spectrum.adobe.com59.3 F58.9 F−0.4
plex.ibm.com53.8 F58.3 F+4.5
pentagram.com70.9 C55.2 F−15.7
fwa.org59.4 F48.0 F−11.4

score-diff-report.json, generated 2026-09-28. Left out, the engine could not read them on that run: nytimes.com, getdesy.com, cssdesignawards.com. Regenerate: node scripts/score-diff.mjs --all.

Blind comparisonagainst axe-core 4.10.2: κ = 0.208, slight agreement

Each site is rated by two instruments that never see each other's results: the engine (accessibility category at 60 or more passes) and axe-core 4.10.2, the WCAG engine, run in a real browser (no serious or critical violation passes). Across 27 sites, κ = 0.208 (95% interval -0.15 to 0.57): slight agreement.

The two measure different layers. The engine reads what shipped CSS declares (tokens, focus-visible, reduced motion); axe reads what renders (computed contrast, ARIA, button names, alt text). Neither contains the other, which is why the full audit at /api/score/audit runs a real browser.

Engine verdict against axe-core 4.10.2 verdict, 27 sites.
Engine verdictaxe-core 4.10.2 passesaxe-core 4.10.2 fails
Engine passes98
Engine fails37

blind-comparison-report.json, generated 2026-09-28. Left out, with no verdict from one of the two raters: nytimes.com, getdesy.com, cssdesignawards.com.

Rank bounds30 weight scenarios; the widest band is 6 places

After sensitivity comes the adversarial test: can a chosen weighting put a site on top? (An analysis of the OECD Better Life Index found 19 of 36 countries could be ranked first.) Every site is recomputed under 30 weightings (the published one, uniform, and each category doubled or halved) and its best and worst rank reported.

On 2026-09-28, the top five held under every scenario. The widest band was awwwards.com, ranks 10 to 16. Ranks here are that run's, which can differ from today's leaderboard.

Each site's rank band across the weight scenariosThe segment runs from the best rank to the worst any scenario gave; the dot is the published rank. Rank 1 is at the left.

rank-bounds-report.json, generated 2026-09-28. Left out, the engine could not read them on that run: nytimes.com, getdesy.com, cssdesignawards.com. Regenerate: node scripts/rank-bounds.mjs.

The numbers
Best, published and worst rank per site across 30 weight scenarios.
SiteBestPublishedWorstBand
designesy.org1110
atlassian.design2242
x.com2364
apple.com3463
primer.style3574
vercel.com4673
wikipedia.org3785
figma.com7892
designesy.ai.studio89102
awwwards.com1010166
roastbyai.com911156
zeroheight.com1012133
vam.ac.uk1213142
linear.app1314163
github.com1115165
m3.material.io1316196
stripe.com1517194
notion.so1718214
radix-ui.com1819246
mozaika.design1720214
geist.dev1921223
stitch.withgoogle.com2022255
carbondesignsystem.com2023255
spectrum.adobe.com2224264
plex.ibm.com2325263
pentagram.com2326263
fwa.org2727270

Every check

All 45, by category, heaviest first. Open a category to read how each of its checks is decided.

Typographyweight 18 · 13 checks · 13 scored

Typography rendering discipline: font smoothing, rem scales, line-height, text-wrap, tabular figures, selection styling, font synthesis, underline position and skip-ink.

  • v14Cadence typography rules match live CSS and contract.cadence Checks for the Cadence rule set: font-synthesis: none, text-underline-position: from-font, text-decoration-skip-ink: auto, -webkit-font-smoothing: antialiased, -moz-osx-font-smoothing: grayscale, root font-size: 16px, all sizes in rem. PASS if all present. These are Designesy Cadence rules, so on an external site (scope=universal) their absence is N/A.
  • v15Font smoothing: antialiased + grayscale on :root confirmed Searches :root or html for -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale. PASS if both are present. Prevents subpixel rendering artifacts on dark backgrounds.
  • v16Rem-based scale: all text sizes in rem, root at 16px confirmed Counts rem-based vs px-based font-size declarations. PASS if the majority are rem and root is 16px. The 16px root is the Cadence floor: iOS Safari auto-zooms inputs below 16px.
  • v17Line-height by role: headings 1.08, body 1.55 confirmed Extracts line-height values from heading and body selectors. PASS if heading line-heights cluster near 1.08 and body line-heights near 1.55. Tight headings read as deliberate; relaxed body copy reads as confident.
  • v18text-wrap: balance + pretty both present in live CSS Searches for text-wrap: balance (headings) and text-wrap: pretty (paragraphs). PASS if both are present. Progressive enhancement: unsupported browsers ignore them. A Designesy Cadence rule, so on an external site (scope=universal) its absence is N/A.
  • v19tabular-nums: 8 instances across the live CSS Counts font-feature-settings: "tnum" or font-variant-numeric: tabular-nums declarations. PASS if ≥ 8 instances (threshold for a site that takes numeric display seriously). Prevents digits from shifting width as values change.
  • v20::selection styled with var(--signal) instead of the browser default Searches for ::selection rules using var(--signal). PASS if the selection color is the signal token instead of the browser default. The selection color is a small but loud brand surface.
  • v26Font family count ≤3 (body + heading + mono) Parses all font-family declarations and counts distinct families. PASS if ≤ 3. WARN if 4 or 5. FAIL if 6+. More than 3 families suggests inconsistency and hurts performance.
  • v46Type sizes come from the scale Reads the type scale from the page's own :root type tokens (names such as --text-*, --font-size-*, --fs-* or --type-* whose value is a static rem or px length; a colour token such as --text-muted never counts), takes their values plus their median ratio as the steps, and the smallest token as the floor. Every static rem or px font-size is judged: off the scale when it sits more than 2.5% from every step, under the floor when it is smaller than the smallest token. A clamp(), min() or max() that moves with a viewport or container unit is deliberate fluid type and is accepted as written; only its lower bound is held to the floor. Relative sizes (em, %, keywords) follow their parent and are not judged. WARN only; SKIP with fewer than three type tokens. v16 asks for rem; this asks whether each rem sits on the scale.
  • v28Reading width 45-75ch on prose containers Parses each CSS rule and keeps its selector alongside its ch value, then asks whether that selector actually targets prose: paragraph-like elements (p, article, li, blockquote) or prose-named classes (.prose, .lede, .measure, .note). Rules on structural selectors (grid, table, row, flex, pre, code) are excluded, because a measure on a grid narrows one track rather than fixing line length. PASS requires a prose-targeting rule in 45 to 75ch (66ch ideal). WARN covers three distinct states, reported separately: ch rules exist but none reach prose; prose rules exist but all are outside the band; or no ch rule at all. Lines longer than 75ch are hard to track; shorter than 45ch feels choppy. Method change 2026-09-17: v28 previously scanned the stylesheet for any max-width in ch units and passed if one value was in range, without checking which selector carried it, which let this site measure 108.6ch on three pages while scoring zero v28 warnings. The check now requires the measure to reach prose. Two leaderboard sites (X, GitHub Primer) moved PASS to WARN under the corrected method, about 0.6 points each; recorded because a method change that moves published grades should be disclosed rather than applied silently.
  • x01font-synthesis: none set (Cadence resolved tension) Searches for font-synthesis: none. PASS if declared. WARN if font-synthesis is declared but not set to none, or if no rule is found. Prevents the browser from synthesizing bold/italic faces when the real weights are not loaded, a common cause of blurry headlines on Windows.
  • x02text-underline-position: from-font set (Cadence resolved tension) Searches for text-underline-position: from-font or under. PASS if declared. Uses the font designer’s built-in underline position rather than the browser default, which is usually too low and clips descenders.
  • x03text-decoration-skip-ink: auto set Searches for text-decoration-skip-ink: auto or none. PASS if declared. Makes underlines skip the rounded parts of letters (g, j, p, q, y), a small typographic refinement that shows attention to craft.
Accessibilityweight 15 · 7 checks · 7 scored

WCAG 2.2 AA primitives: contrast, touch targets, heading hierarchy, the input font floor, button text contrast, forced-colors readiness and status colors used as text in every theme. It carries the accessibility floor.

  • v06Contrast remains readable for ink, muted, and accent on paper (WCAG 2.1 + APCA) Resolves --paper, --ink, --muted, --muted-dim to RGB and computes WCAG 2.1 contrast ratios. PASS if all clear 4.5:1 (AA body text). WARN if any clear 3:1 but not 4.5:1. FAIL if any below 3:1. APCA Lc values are reported alongside.
  • v22Primary button text passes WCAG AA contrast against --signal fill Resolves --signal to RGB, then tests --ink and --paper against it. PASS if the best ratio ≥ 4.5:1. WARN if ≥ 3:1 (large-text pass). FAIL if below 3:1. N/A if --signal is not declared or unresolvable.
  • v24Touch targets ≥44px on interactive elements (WCAG 2.5.5 Enhanced) Searches CSS for min-height or min-width ≥ 44px on button/a/input/select selectors. PASS if found. 44px is WCAG 2.2 SC 2.5.5 Target Size (Enhanced), level AAA; the AA minimum, SC 2.5.8, is 24px, and the contract holds the stricter bar. Full verification needs a browser to measure rendered dimensions.
  • v25Heading hierarchy: single h1, no skipped levels Parses the HTML for h1-h6 elements. PASS if exactly one h1 and no skipped levels (no h1→h3 jumps). Screen readers and SEO both rely on a logical heading outline.
  • v27Input font-size ≥16px (prevents iOS Safari auto-zoom) Searches CSS for input/textarea/select font-size declarations. FAIL if one is below 16px, PASS if the 16px (1rem) floor is declared. With neither, WARN if the page has a text input, textarea or select; N/A if it has none, since nothing can zoom. Inputs below 16px trigger a layout-shift zoom on iPhone that breaks mobile UX.
  • v35Forced-colors readiness: @media (forced-colors: active) block present Searches CSS for @media (forced-colors: active) and forced-color-adjust. PASS if both are present. Windows High Contrast Mode and Chrome forced-colors recolor the page; without this media query, critical UI becomes illegible.
  • v44Status colors used as text meet contrast in every declared theme Finds the status colors (custom properties named ok, success, warn, warning, error, danger, info, grade-a to grade-f and the like, or a hue the stylesheet also paints as a mark) that a rule uses as text color, directly, through one alias or at reduced alpha. Resolves each in every declared theme (:root, [data-theme] or .dark/.light blocks, prefers-color-scheme blocks; a theme named by part of a compound theme selector also reads that compound’s tokens), including var() chains and color-mix() rounded to the 8-bit color a browser paints. Measures it on a surface the stylesheet attests: the background the rule or another rule for the same element sets, else the nearest ancestor’s in the same selector (a hovered button’s own fill, else its fill at rest), translucent fills composited, else the page background of that theme. PASS if every use clears 4.5:1 (3:1 for 24px, or 18.66px bold; 3:1 for a graphic: an svg, an icon or glyph, a progress bar, a color named for an icon, a currentColor fill) in every theme. FAIL if a color named as a status falls short where it has a hue (Oklch chroma 0.06 or more). WARN if white or grey text under a status name falls short (a general text-contrast miss: the status lives in the fill behind it), if a hue that counts only because the sheet also paints it as a mark falls short (its real surface may be a fill the static engine cannot see), if an ancestor in a hover, active, focus, expanded or open state paints no fill the sheet declares (the engine never FAILs a pairing it cannot attest), if a background cannot be resolved, or if alpha-reduced text clears only at full alpha. Disabled states are exempt, as WCAG 1.4.3 exempts inactive controls. N/A if no status color is used as text. Found on this site: light --warn as text measured 3.51:1.
Color rolesweight 12 · 2 checks · 2 scored

Whether the color system speaks in roles (ink, surface, danger) or in hues (blue-500), and covers the status states. Wired on 2026-08-30, so batch runs before that date score it empty.

  • v42Semantic color vocabulary: tokens named by role rather than hue Classifies every color-valued token in :root by name: role words (ink, paper, surface, muted, danger, success, accent, border…) vs hue words (blue, slate, amber…) and numeric scale suffixes (-500). Brand coinages matching neither list are excluded from the share. PASS if ≥60% role-named with ≥3 distinct roles; WARN otherwise (WARN-only, because it is style craft with no user harm). SKIP if fewer than 4 color tokens. Mirrors the contract's own role-named palette.
  • v43Semantic status colors: ok/warn/error/info state roles present Scans color token names for the four status families: ok/success, warn/warning/caution, error/danger/fail, info/notice. PASS if ≥3 of 4 families are present; WARN otherwise (WARN-only). SKIP if the site declares no color tokens. The contract ships --ok, --warn, and --error for exactly this purpose.
Motionweight 10 · 5 checks · 5 scored

Motion hygiene: no transition: all, will-change kept to transform and opacity, a reduced-motion block, duration tokens, and content that stays visible when motion is paused.

  • v05prefers-reduced-motion disables entrance and wordmark breath Reads each @media (prefers-reduced-motion) block by its value. PASS if a reduce block declares rules, or if a no-preference block opts into motion (an animation, a transition, smooth scrolling or a view transition), which keeps that motion from anyone who asked for less. WARN if neither: an empty block, or a no-preference block that only switches motion off, honours nothing. This is the vestibular-safety primitive: without it, motion-sensitive users cannot use the site.
  • v11No transition:all in the live stylesheet Regex-searches for transition: all (case-insensitive). FAIL if found. transition: all causes layout-thrash and surprises; the contract requires named properties only.
  • v12will-change restricted to transform and opacity only Parses every will-change declaration. If any value contains anything other than transform or opacity (including auto), it WARNs. will-change on non-transform/opacity properties forces unnecessary layer promotion.
  • v23Duration tokens --duration-quick through --duration-slow present in :root Checks :root for the 5 duration tokens: --duration, --duration-quick, --duration-fast, --duration-medium, --duration-slow. PASS if all 5 are declared. Hardcoded ms values in component CSS are the anti-pattern.
  • v45Pausing motion keeps content visible Finds a pause that holds every element: animation-play-state: paused on * under an attribute or class on the document (html[data-motion="paused"]), or inside prefers-reduced-motion: reduce. Then finds each one-shot entrance whose first keyframe sets opacity 0 or visibility hidden, which the pause would hold invisible. PASS if every such entrance has an override under the pause (animation: none, animation-name: none, animation-play-state: running, or a negative animation-delay at least as long as the animation) for its selector or a compound it contains. FAIL if an entrance has none. WARN if the override exists but its selector or scope does not clearly match. N/A if no rule pauses every element. Found on this site: paused visitors got empty pages.
Tokensweight 9 · 2 checks · 2 scored

Token architecture: the --paper foundation, and whether tokens are layered from primitive to semantic to component.

  • v01Token values match live site :root foundation Parses :root custom properties from the fetched CSS. Looks for --paper specifically: the contract names --paper, --ink, --muted, --surface, --surface-raised, --line, --signal, --signal-light, --signal-dim as the required foundation. PASS if --paper resolves to a value.
  • v29Token architecture: primitive → semantic → component layers Counts how many tokens are referenced via var() (aliasing) vs. raw values. A 2-tier or 3-tier aliasing structure (primitive → semantic → component) indicates a mature token system. PASS if at least 2 layers are detected.
Copywritingweight 8 · 4 checks · 4 scored

UX copy discipline: verb-led buttons, no trailing periods, descriptive link text, no all-caps. Heuristics grounded in NN/g, Microsoft Fluent, IBM Carbon and WCAG 2.4.4.

  • v38Button text is a verb phrase or recognized command (not a bare noun) Parses button elements and checks if text starts with a verb or recognized command (Save, Cancel, Delete, Edit, Share, Close, Back, Next). WARN if buttons don't lead with a verb. N/A if no buttons found. Heuristic: review flagged buttons manually. Grounded in NN/g: "Lead with verbs or verb phrases that clearly outline what will happen after the command is selected."
  • v39No trailing period on button text, labels, or tab text Searches button/label/tab elements for trailing periods (excludes ellipsis ...). WARN if found. N/A if no relevant elements. Microsoft Fluent: "Don't end text for buttons, radio buttons, labels, or checkboxes with a period." Periods are for full sentences in tooltips, error messages, and dialog bodies only.
  • v40Link text is descriptive (not bare "click here", "learn more", "here") Parses anchor elements and checks link text against a blocklist of non-descriptive patterns (click here, here, learn more, read more, more, link, this, that, continue, see more, view details). WARN if matched. N/A if no anchors. WCAG 2.4.4 Link Purpose: link text should describe the destination.
  • v41No ALL CAPS UI text except eyebrow labels Searches button/a/label/td/th/p/li/h1-h6 for ALL CAPS text (>3 letters), excluding elements with class containing "eyebrow"/"meta-label" or inline text-transform: uppercase. WARN if found. IBM Carbon: "All caps has been shown to be slower to read." Only eyebrow labels and acronyms should be uppercase.
Interface feelweight 8 · 2 checks · 2 scored

Interaction feel: press scales above the 0.95 floor (0.96 for cells, 0.985 for cards, 0.995 for surfaces). Named for the German word for precise, musical timing.

  • v10Takt interface-feel rules match live CSS and contract.takt Verifies the static half: stagger enter animation-delay, soften exit transform ease-out, concentric border-radius set. Press-behavior and hit-area require a browser.
  • v13Press scale 0.96 on cells, 0.985 on cards/rows (both above the 0.95 floor) Extracts every transform: scale() value in :active contexts. FAIL if any scale is 0 (a glitch rather than a press) or below 0.95. PASS if real press scales are found above 0.95. The 0.95 floor is the contract minimum; lower reads as a glitch.
Control polishweight 7 · 3 checks · 2 scored

Interaction poise: hover lifts, press-settle, keyboard-path documentation and the sound toggle. The static half is read from CSS; the interaction half needs a browser.

  • v04manualSound toggle flips aria-pressed and applies the audio preference Requires clicking a sound toggle and verifying aria-pressed flips and a [data-audio] attribute is applied. The static engine cannot interact with the DOM.
  • v08Poise interaction rules match live /labs/poise and contract.interaction Verifies the static half: fine-pointer hover guard (@media hover: hover), press-settle scale ~0.97, opacity-only mark breath. The interaction-feel half requires a browser.
  • v09Poise keyboard-path verification remains published and current Verifies the static half: 4 keyboard-affordance signals in the HTML/CSS (tabindex, accesskey, key bindings, focus management). Tab-order traversal requires a browser.
Page basicsweight 6 · 2 checks · 2 scored

Document identity: semantic landmarks (h1, title, meta description, main, header, nav) and AI-disclosure readiness (EU AI Act, Article 50).

  • v07Semantic HTML foundation: single h1, title, meta description, landmark Parses HTML for: exactly one h1, a descriptive <title>, <meta name="description">, and at least one <main>/<header>/<nav> landmark. PASS if all 4 are present. WARN if 1-2 missing. FAIL if 3+ missing.
  • v34AI-Disclosure Readiness (EU AI Act Art 50, effective 2026-08-02) Detects AI-interactive surfaces (chatbots, AI assistants) in the HTML. If detected, checks for disclosure signals (visible "AI" text, aria-label, meta generator, C2PA). PASS if no AI surface is detected (disclosure not required) or if disclosure is present. FAIL if an AI surface is detected without disclosure.
Interactionweight 6 · 1 check · 1 scored

Focus visibility: :focus-visible rings declared.

  • v03Primary interactive elements show focus-visible rings Regex-searches the CSS for :focus-visible declarations. PASS if any :focus-visible rule is found. This is the keyboard-navigation visibility primitive: without it, Tab users cannot see where they are.
Performanceweight 6 · 1 check · 0 scored

Core Web Vitals (LCP, INP, CLS). They need a CDP or Playwright trace, so the static engine marks them manual; the weight is held in reserve.

  • v21manualCore Web Vitals plausible: LCP < 2.5s, INP < 200ms, CLS < 0.1 Requires a CDP/Playwright trace to measure LCP, INP, and CLS against the Google thresholds. The static engine cannot do this.
Securityweight 5 · 1 check · 1 scored

Unicode security: UTS #39 confusable detection in token names and CSS identifiers, against Cyrillic and Greek homoglyphs shadowing Latin names.

  • v36Unicode Security: no UTS #39 confusable characters in token names or CSS identifiers Scans token names, CSS class/id selectors, and url() refs for non-ASCII confusable characters (Cyrillic, Greek, fullwidth) using a Unicode confusable detector. PASS when 0 confusables. FAIL when token-name confusables found (shadowing risk, e.g. --соlor-bg with Cyrillic с vs --color-bg). WARN for class/id/url confusables. Provenance: Unicode Technical Standard #39, Unicode 16.0.0.
DESIGN.mdweight 4 · 1 check · 1 scored

The DESIGN.md spec layer: the DESIGN.md format’s linter, run when a site serves /DESIGN.md and not applicable otherwise.

  • v37conditionalDESIGN.md spec-layer validation (DESIGN.md format lint) Fetches /DESIGN.md from the target origin and runs the DESIGN.md format's linter (11 lint rules: broken token refs, missing primary colors, WCAG contrast, orphaned tokens, section order). PASS on clean lint. WARN on lint warnings. FAIL on lint errors. N/A if /DESIGN.md is not served; this is expected, as no public convention requires it yet.
Responsiveweight 3 · 1 check · 0 scored

Horizontal overflow at 375, 720, 860 and 1080 px and up. It needs a browser viewport, so the static engine marks it manual; the weight is held in reserve.

  • v02manualRoutes render without horizontal overflow at 375px, 720px, 860px, 1080px+ Requires rendering the page at four viewport widths and measuring scrollWidth > clientWidth. The static engine cannot do this.

Data exports

/api/leaderboard
JSON with each site's category scores, its status (current and ranked, or stale: last scored on an earlier run because the latest could not re-score it), its relationship to Designesy (operator or competitor) with a disclosure, and the address for disputes. CORS-enabled.
/api/leaderboard.csv
RFC 4180 CSV with a header row, for spreadsheets. The same status, relationship and disclosure columns.

Contract v0.4.6 · engine 1.5.0 · 45 checks in 14 categories · 12 slop rules, up to −20 · originality, up to +8 · 8 hard-fail ceilings · engine at /api/score · contract at /contracts/design-system.json