Design-quality score
UICraftScore — a deterministic 0-100 composite grade from anti-slop, token discipline, and a11y. Plus UsabilityScore, the judged companion.
Updated 2026-06-25
UICraftScore turns three static-analysis signals into one defensible 0-100 grade. It’s objective, reproducible evidence of design quality — not vibes.
score = 100
− (antiSlop_critical × 8) − (antiSlop_major × 4) − (antiSlop_warn × 1)
− (token_findings × 2)
− (a11y_critical × 8) − (a11y_major × 4)
clamped [0,100] · A ≥ 90 · B ≥ 80 · C ≥ 70 · D ≥ 60 · F < 60
Three dimensions, each with its own subscore:
| Dimension | Source | Penalty |
|---|---|---|
| anti_slop | 33 rules from ui-craft-detect | critical −8 · major −4 · warn −1 |
| token_discipline | raw hex / off-scale radius / spacing / z-index | −2 per finding (flat) |
| a11y | 5 static checks (no overlap with the detector): img-no-alt, non-semantic-interactive, positive-tabindex, aria-invalid-no-describedby, no-reduced-motion |
critical −8 · major −4 |
It’s deterministic by design — identical input always yields the identical score, which is exactly what makes it safe to gate CI on. Formula, weights, and bands are published in evals/quality/score.mjs; hand-authored fixtures + baselines.json form the regression gate.
CLI
# score a single file
node scripts/eval.mjs src/components/Hero.tsx
# score a directory
node scripts/eval.mjs src/components/
# run the full regression gate (slop vs. designer separation)
node scripts/eval.mjs --baseline
# JSON output (CI-friendly)
node scripts/eval.mjs src/components/Hero.tsx --json
# fail below a threshold (default 70)
node scripts/eval.mjs src/components/Hero.tsx --threshold 80
Exit codes: 0 clean / in-band · 1 below threshold or out of band · 2 arg error.
Via MCP
The score_ui tool exposes the same scorer to any MCP client:
{ "code": "<your tsx source>" } // or: { "path": "src/components/Hero.tsx" }
Returns { overall: { score, grade }, dimensions: { anti_slop, token_discipline, a11y }, version } — the same envelope as the CLI --json.
UsabilityScore
UICraftScore is deterministic, which also bounds it: static analysis can’t see experience friction — a confusing flow, a missing undo, a 2-second save with no feedback. UsabilityScore covers that axis. It rolls the /heuristic scorecard (Nielsen’s 10 + 6 design laws) into one 0-100 number:
heuristic_base = round( ((mean(nielsen_scores) − 1) / 4) × 100 ) # 10 scores, each 1–5
UsabilityScore = clamp( heuristic_base − 5 × (failed design laws) , 0 , 100 )
Same bands as UICraftScore: A ≥ 90 · B ≥ 80 · C ≥ 70 · D ≥ 60 · F < 60
It is judged, not deterministic — computed by the host agent from the rubric (zero deps, no API key, every harness), so it may vary run to run. Gate CI on UICraftScore; use UsabilityScore for review depth.
Extended report
When you want the full picture, the two render side by side — each labeled for reproducibility:
| Score | Value | Reproducible? |
|---|---|---|
| UICraftScore | 82 / B | ✅ deterministic — identical every run |
| UsabilityScore | 33 / F | ❌ judged — LLM judgment, may vary |
They are deliberately never averaged: one is reproducible and one is judged, and collapsing them would hide that distinction — the exact property the deterministic score exists to protect. A high UICraftScore with a low UsabilityScore means the code is clean but the experience has friction the static rules can’t see.
Source
- Scorer:
evals/quality/score.mjs - UsabilityScore rubric:
heuristics.md
Spotted something out of date? Open an issue on GitHub →