Design Tally
A counting pass, not a taste guide. Other anti-slop skills state principles; principles get evaded one justification at a time. This skill makes the evasion visible by forcing a count.
Why this exists
A real failure this skill was built from: eight design variants were produced in one session, each using a small tracked all-caps label for section headings. Every instance had a local justification that sounded reasonable in the moment. Nobody added them up. The total was 37, across five variants that had been presented as visually distinct. The governing principle ("use hierarchy before labels") was loaded and available the whole time.
A count is not evadable the way a judgment is. Run the count.
The rule
Before presenting any UI work — every time, no exceptions — produce the tally below and include it in the message. Not a summary of it. The table, with numbers.
If a count is above zero, either fix it or write the specific justification for each instance. "It felt right here" is not a justification. If the same pattern appears across multiple screens or variants, that is the finding, regardless of how each instance reads alone.
Never report the tally as clean without having actually inspected the output. Read the markup or styles; do not infer from memory of what was written.
Banned patterns
Hard bans. These are not available unless the user explicitly asks for them by name in the current request. A design brief declaring them is not sufficient.
| # | Pattern | Detection |
|---|---|---|
| 1 | Tracked or mono all-caps eyebrows | text-transform: uppercase AND (monospace family OR letter-spacing >= 0.05em) |
| 2 | Monospace on prose | mono family on any string that is not code, a literal value, an identifier, or a number |
| 3 | Heading weight 700 or heavier | any heading or display text at font-weight >= 700 |
| 4 | Italic display type | font-style: italic at heading or display sizes |
| 5 | Border and shadow on one element | both border and box-shadow on the same node |
| 6 | Leading dot in a pill | circle, dot, or small filled shape as the first child of a pill, chip, badge, or tag |
| 7 | Lucide icons | the Lucide set, or hand-drawn SVG that reproduces its shapes (1.5px stroke, rounded caps, 24x24 geometric glyphs). Hand-drawing converges on Lucide by default — assume any hand-drawn icon is a violation until checked against the approved set |
Counted and justified per instance, not banned outright:
| # | Pattern | Detection |
|---|---|---|
| 8 | Dividers | count every rule, hairline, and border-as-separator per screen. Each needs a reason whitespace or proximity could not serve |
| 9 | Container cards | count every bordered or filled box per screen. Flag any nested directly inside another |
| 10 | Excess text | prose that restates what an adjacent value, label, or state already communicates |
| 11 | Same slot across variants | when producing N variants presented as distinct, any slot — section label, footer, header, button shape, spacing rhythm — rendered identically in all N |
Rule 11 is the cross-variant check. Compare the same slot across every variant before presenting a set. Distinct moods with an identical label treatment are not distinct variants.
Icons
Approved set: Phosphor. Use it rather than drawing glyphs by hand.
Before adding any icon, ask whether it carries information a word could not. If a text label works, use the text label.
Running the linter
For UI that exists as code, run the script before hand-counting:
python3 ~/.claude/skills/design-tally/scripts/tally.py <path> [--json] [--quiet]
It covers rules 1-6 and prints the tally table. Exit code is 1 only when a provable rule (1, 5, 6) has a hit, so it can gate a hook or CI step without failing on candidates.
Rules 2, 3 and 4 are reported as candidates, not violations — the script can see a mono family or a 700 weight but cannot tell prose from code, or a heading from a label. Classify each candidate yourself and say which way you ruled.
The script cannot see rules 7-11, and cannot read Paper or Figma artboards at all. For design-tool work, count by hand using get_jsx or the equivalent — never from a screenshot, and never from memory of what you wrote.
A clean script run is not a clean tally. It covers 6 of 11 rules on code only.
Output format
Report every rule, including the ones at zero — a zero is information, and listing only the hits hides whether the pass actually ran.
Design tally
| Rule | Count | Where |
|------|-------|-------|
| 1 eyebrows | 0 | — |
| 2 mono on prose | 2 | caption under hero; empty-state line |
| ... | | |
Then, for each nonzero count: fix it, or state the per-instance justification.
Adding to this list
When the user identifies a new pattern, add it to the table with a detection rule specific enough to count. If it cannot be stated as a detectable condition, it belongs in a principles skill, not this one.