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 | 43 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.
Does the number actually discriminate?
A score is only worth gating on if it separates the two classes it claims to separate. Eleven fixtures
are held as the regression gate, each with a banded expected range, and node scripts/eval.mjs --baseline
fails if any drifts out of its band. Current run, 11 of 11 passing:
| Class | Fixture | Score |
|---|---|---|
| Slop | all-violations.tsx |
0 |
| Slop | div-onclick-hex.tsx |
40 |
| Slop | purple-gradient.tsx |
48 |
| Slop | animation-no-motion.tsx |
60 |
| Slop | auth-brand-flood.tsx |
60 |
| Slop | landing-template-tells.tsx |
68 |
| Designer | clean-card.tsx |
100 |
| Designer | accessible-form.tsx |
100 |
| Designer | semantic-image.tsx |
100 |
| Designer | motion-safe.tsx |
100 |
| Designer | product-shell.tsx |
100 |
Six slop fixtures average 46; five designer fixtures score 100. No overlap between the classes.
Two things this does not claim, so it is worth being explicit. These are hand-authored fixtures representing each class, not the same prompt run twice — so the gap measures whether the scorer discriminates, not how much installing UI Craft improves a given project. And the fixtures are React with Tailwind; the detector’s rules are strongest on that shape, and weaker on raw CSS.
A measured before-and-after on identical prompts is a different experiment. It has not been run, and until it has, no number here should be read as one.
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 →