jig-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +115 -0
- package/dist/index.js +544 -0
- package/package.json +61 -0
- package/rules/00-anti-patterns.md +501 -0
- package/rules/01-modes.md +213 -0
- package/rules/02-tokens.md +210 -0
- package/rules/03-patterns.md +459 -0
- package/rules/04-principles.md +154 -0
- package/rules/05-copy.md +153 -0
- package/rules.index.json +637 -0
- package/templates/SKILL.md.tmpl +53 -0
- package/templates/command-metadata.json +27 -0
- package/tokens/brand.default.css +152 -0
- package/tokens/mode.editorial.css +72 -0
- package/tokens/mode.operator.css +74 -0
- package/tokens/mode.product.css +69 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# 00 · Anti-Patterns
|
|
2
|
+
|
|
3
|
+
**Status:** draft v0.1
|
|
4
|
+
**Scope:** universal. Every rule here is wrong in *all* modes (`editorial`, `product`, `operator`). Anything that depends on mode belongs in that mode's profile, not here.
|
|
5
|
+
**Floor:** WCAG 2.1 level AA. Not an aspiration — the minimum this system is built on.
|
|
6
|
+
**Numbering:** rule numbers are stable identifiers, not an ordering. A new rule takes the next free number and sits in its topic section. Numbers are never reused or renumbered, so a citation stays valid.
|
|
7
|
+
**Framework:** agnostic. Examples are stated in CSS properties and token names. Where a utility-class framework is in use, translate — the rule is about the resulting style, not the syntax.
|
|
8
|
+
|
|
9
|
+
## How to use this file
|
|
10
|
+
|
|
11
|
+
You are generating or reviewing UI. Treat every rule below as a hard constraint unless the task explicitly overrides it.
|
|
12
|
+
|
|
13
|
+
- Rules are numbered (`A-14`). Cite the number when you follow or deliberately break one.
|
|
14
|
+
- Each rule has a **correction**, not just a prohibition. Apply the correction; do not substitute your own default.
|
|
15
|
+
- If a rule conflicts with an explicit instruction in the task, the task wins — but say so in one line rather than silently deviating.
|
|
16
|
+
- Before finishing, run the checklist at the end.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## A. Generic-AI aesthetic
|
|
21
|
+
|
|
22
|
+
These are the strongest defaults in a model's training data and the fastest way to make work look machine-made. They resurface every session; assume they will.
|
|
23
|
+
|
|
24
|
+
### A-01 Purple and violet as the unspecified default
|
|
25
|
+
❌ A violet or indigo fill, or a violet→pink gradient, chosen because no colour was specified
|
|
26
|
+
✅ Use `--color-brand` from the brand file. The unbranded default resolves it to near-black, which ships a coherent monochrome UI and makes the missing decision visible. Then ask.
|
|
27
|
+
|
|
28
|
+
### A-02 Gradient text on headings
|
|
29
|
+
❌ `background-clip: text` with a gradient fill and transparent text colour
|
|
30
|
+
✅ Solid `--color-text-strong`. Gradient text has unmeasurable contrast and reads as template output.
|
|
31
|
+
|
|
32
|
+
### A-03 Decorative gradient blobs and orbs
|
|
33
|
+
❌ Absolutely-positioned blurred radial shapes behind a hero
|
|
34
|
+
✅ Flat background, or a single subtle surface change. Backgrounds do not need decoration to justify existing.
|
|
35
|
+
|
|
36
|
+
### A-04 Trend styles that fight legibility
|
|
37
|
+
❌ Glassmorphism (translucent fill + `backdrop-filter: blur()`), neumorphism (soft inset/outset shadows on a matching background), and their successors
|
|
38
|
+
✅ Opaque `--color-surface` with a `--color-stroke-weak` edge. Use translucency only over media, and only when legibility is verified against the worst frame.
|
|
39
|
+
Both styles make sufficient contrast and clear hierarchy structurally difficult — neumorphism in particular defines every element with shadow alone, which fails at 3:1 almost by construction. Trend styles also age badly: the more of them a product carries, the more precisely it is dated. Minimal styling that foregrounds content lasts longer.
|
|
40
|
+
Experiment freely — but not where it costs legibility or excludes people.
|
|
41
|
+
|
|
42
|
+
### A-58 Decorative styling that implies meaning
|
|
43
|
+
❌ List items in assorted colours chosen for variety; a decorative icon beside a heading that looks pressable; a heading coloured and underlined though it is not a link
|
|
44
|
+
✅ Style carries information or it goes. People assume differences mean something — arbitrary colour invites them to hunt for a pattern that does not exist, and an icon that looks interactive will be clicked.
|
|
45
|
+
Decoration is allowed. Decoration that mimics a functional signal is not.
|
|
46
|
+
|
|
47
|
+
### A-59 Repeated information
|
|
48
|
+
❌ Every item in a list restating the shared context: "UI Course – Chapter 1", "UI Course – Chapter 2"
|
|
49
|
+
✅ Lift the shared part into a heading above the list and let each item carry only what differs. Repetition costs space, adds reading, and buries the part the user is actually scanning for.
|
|
50
|
+
|
|
51
|
+
### A-60 Icons all competing at equal weight
|
|
52
|
+
❌ A row of large, high-contrast icons beside secondary text
|
|
53
|
+
✅ Icons supporting text are subordinate to it — smaller, lower contrast, or both. An icon at the same visual weight as the content it decorates competes with it for attention it does not deserve.
|
|
54
|
+
|
|
55
|
+
### A-67 A container around every group
|
|
56
|
+
❌ Borders, cards and background panels wrapping each cluster on the page
|
|
57
|
+
✅ A container is the **strongest** grouping cue and the heaviest. Reach for it last.
|
|
58
|
+
Four tools group elements, in ascending strength: **continuity** (aligned in a line), **similarity** (same size, shape, colour), **proximity** (closer together than to anything else), **common region** (a shared container). Use the weakest one that works.
|
|
59
|
+
Where several already apply — a table's rows are aligned, alike, and close — the container adds clutter and no information. Remove it and check whether the grouping still reads. It usually does.
|
|
60
|
+
|
|
61
|
+
### A-05 Emoji as interface iconography
|
|
62
|
+
❌ `<h3>🚀 Fast deploys</h3>`, emoji bullets in feature lists
|
|
63
|
+
✅ A real icon set, or no icon. Emoji render inconsistently across platforms, carry unpredictable screen-reader announcements, and are almost never in the brand.
|
|
64
|
+
|
|
65
|
+
### A-06 The three-column feature grid reflex
|
|
66
|
+
❌ Icon-in-rounded-square + heading + two lines, three across, for any set of three things
|
|
67
|
+
✅ Let the content pick the layout. Three items of unequal weight are a list, not a grid.
|
|
68
|
+
|
|
69
|
+
### A-07 Oversized radius everywhere
|
|
70
|
+
❌ A single large radius (16px+) applied to cards, buttons, inputs and badges alike
|
|
71
|
+
✅ `--radius-control` for controls, `--radius-surface` for containers. Both derive from `--radius-sm`, which is a brand decision. Small elements take small radii.
|
|
72
|
+
|
|
73
|
+
### A-08 Shadow as the only depth cue
|
|
74
|
+
❌ A drop shadow on every card, or several shadow sizes with no rule governing which means what
|
|
75
|
+
✅ `--shadow-surface` is `none` in all three modes. Depth comes from `--color-stroke-weak` or a surface-colour step. `--shadow-raised` exists for overlays only — dialogs, popovers, dropdowns.
|
|
76
|
+
|
|
77
|
+
### A-09 Marketing voice in an application
|
|
78
|
+
❌ "Supercharge your workflow" on an internal dashboard
|
|
79
|
+
✅ Copy matches the context. Operator tools name the object and the action.
|
|
80
|
+
|
|
81
|
+
### A-10 Placeholder content shipped
|
|
82
|
+
❌ Lorem ipsum, "Acme Inc", `https://example.com`, stock avatars left in
|
|
83
|
+
✅ Real content, or clearly marked `TODO:` that fails a build check. Placeholder text that survives to review costs a reviewer more than it saved you.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## B. Typography
|
|
88
|
+
|
|
89
|
+
### B-11 Unbounded line length
|
|
90
|
+
❌ Paragraphs spanning the full width of a wide viewport
|
|
91
|
+
✅ Cap at `--measure-prose` (68ch editorial, 60ch product, 72ch operator). Applies to any run of prose in any mode.
|
|
92
|
+
|
|
93
|
+
### B-12 Centred or justified body text
|
|
94
|
+
❌ A centred paragraph of four lines. Justified text of any length.
|
|
95
|
+
✅ Start-align anything longer than a short heading or label.
|
|
96
|
+
**Centred** text moves the start of every line, so the eye hunts for it. Acceptable for a heading or a couple of lines; never for a paragraph.
|
|
97
|
+
**Justified** text is worse and has no acceptable case here. Stretching word spacing to force a straight right edge creates uneven gaps and vertical "rivers" of white space running down the block — actively harmful for dyslexic readers, and the reason books that justify are harder to read than they look.
|
|
98
|
+
|
|
99
|
+
### B-13 One line-height for everything
|
|
100
|
+
❌ One line-height value applied to both a 48px heading and 16px body
|
|
101
|
+
✅ `--leading-body` for prose, `--leading-heading` for headings, `--leading-display` for the largest tier. Line height decreases as size increases.
|
|
102
|
+
|
|
103
|
+
### B-14 Thin weights for body text
|
|
104
|
+
❌ Weight 300 or lighter for paragraphs
|
|
105
|
+
✅ `--font-weight-body` (400) minimum. Thin weights fail on low-density screens and in bright light — both of which describe most of your users' actual conditions.
|
|
106
|
+
|
|
107
|
+
### B-15 Ad-hoc type sizes
|
|
108
|
+
❌ A one-off `font-size` because something looked slightly wrong
|
|
109
|
+
✅ Use a `--text-*` step. If the scale genuinely lacks one, add it to the mode file rather than bypassing the scale at the call site.
|
|
110
|
+
|
|
111
|
+
### B-16 All-caps for anything long
|
|
112
|
+
❌ All-caps sentences, all-caps buttons with three or more words
|
|
113
|
+
✅ Reserve caps for short labels, and add letter-spacing when you do. Caps destroy word-shape recognition.
|
|
114
|
+
|
|
115
|
+
### B-17 Skipping heading levels for visual reasons
|
|
116
|
+
❌ `<h4>` because `<h2>` looked too big
|
|
117
|
+
✅ Correct level, styled to the size you want. Heading order is document structure, not typography.
|
|
118
|
+
|
|
119
|
+
### B-75 Long-form text below 18px
|
|
120
|
+
❌ 14px or 16px body text for an article, description or any sustained reading
|
|
121
|
+
✅ `--text-prose` (18px) with `--leading-prose` (1.6) for long-form text. `--text-body` (16px) is for **UI text** — labels, controls, table cells, short strings — which is read in glances rather than sustained passages.
|
|
122
|
+
People read at roughly arm's length on every device. Small type is a designer's preference, not a reader's. When the text is a paragraph someone must actually get through, size up.
|
|
123
|
+
|
|
124
|
+
### B-76 More than two typefaces
|
|
125
|
+
❌ A serif for headings, a script for the description, a sans for the UI
|
|
126
|
+
✅ One sans serif for everything is the safe default: most legible at small sizes, neutral enough to suit any brand, and it keeps the content rather than the lettering as the focus.
|
|
127
|
+
A **second** typeface is permitted for headings only, where the brand needs a mood the sans cannot carry. Never a third. Never a script or display face below heading size — they are drawn for large sizes and become unreadable small.
|
|
128
|
+
|
|
129
|
+
### B-77 More than two font weights
|
|
130
|
+
❌ Thin, light, regular, medium, semibold and bold across one interface
|
|
131
|
+
✅ Two: `--font-weight-regular` (400) and `--font-weight-bold` (600). Bold for headings and emphasis, regular for everything else.
|
|
132
|
+
Extra weights read as noise, are impossible to apply consistently, and add a decision to every text element. Reserve very thin or very heavy weights for large display text, where they are legible.
|
|
133
|
+
|
|
134
|
+
### B-78 Text placed directly on a photo
|
|
135
|
+
❌ White text over an image, legible on the mockup's photo and not on the next one
|
|
136
|
+
✅ Contrast still has to be met — 4.5:1 for text 18px and under, 3:1 for larger. A photo cannot guarantee it, because the photo changes.
|
|
137
|
+
Four workable treatments:
|
|
138
|
+
1. **Linear gradient overlay** — dark, ~90% opacity at the text edge fading to 0% partway across. Preserves most of the image.
|
|
139
|
+
2. **Semi-transparent overlay** — a flat dark layer at ~50% over the whole image.
|
|
140
|
+
3. **Blurred semi-transparent overlay** — as above with a blur behind the text only.
|
|
141
|
+
4. **Solid background behind the text** — the caption approach; most reliable, least subtle.
|
|
142
|
+
A text shadow may reinforce any of these but never substitutes for one. Verify against the worst image the slot will ever hold, not the one in the mockup.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## C. Colour and contrast
|
|
147
|
+
|
|
148
|
+
### C-66 Depth built from shadow in dark mode
|
|
149
|
+
❌ The same shadow tokens carried into dark mode to lift cards off the page
|
|
150
|
+
✅ In dark mode, elevation comes from the **background colour** — `bg-base` → `bg-raised` → `bg-overlay`, each lighter than the last. Shadows are close to invisible on a dark surface. `--shadow-raised` resolves to `none` in dark for exactly this reason.
|
|
151
|
+
Also: shadow colour derives from the text colour, never pure black, so it sits inside the palette rather than on top of it.
|
|
152
|
+
|
|
153
|
+
### C-18 Pure black on pure white
|
|
154
|
+
❌ `#000` text on `#fff`
|
|
155
|
+
✅ Near-black on off-white. Full-contrast black/white causes halation and reads as unconsidered. This is a house preference and it is deliberate.
|
|
156
|
+
|
|
157
|
+
### C-19 Grey text below contrast floor
|
|
158
|
+
❌ A mid-grey (ramp step `-500` or lighter) used for secondary text, placeholders or timestamps
|
|
159
|
+
✅ `--color-text-weak` for secondary text, `--color-text-weak` for large text only. See the contrast contract in `02-tokens.md`: `-500` and lighter are never text on a light background. Check placeholders and disabled states specifically; they are the usual failures.
|
|
160
|
+
|
|
161
|
+
### C-20 Colour as the sole signal
|
|
162
|
+
❌ Red border alone to indicate an invalid field
|
|
163
|
+
✅ Colour plus text plus (where useful) an icon. Applies to status badges, chart series, diff views, and validation.
|
|
164
|
+
|
|
165
|
+
### C-21 Dark mode by inversion
|
|
166
|
+
❌ Flipping to `#000` background and keeping the same shadows and borders
|
|
167
|
+
✅ Dark mode is its own ramp: elevated surfaces get *lighter*, shadows lose most of their meaning, and saturated colours need desaturating. If dark mode is out of scope, say so rather than shipping a broken one.
|
|
168
|
+
|
|
169
|
+
### C-22 Semantic colours invented inline
|
|
170
|
+
❌ Two different reds in two places, both meaning "error"
|
|
171
|
+
✅ One token per meaning — `--color-danger`, `--color-danger-subtle`, `--color-danger-stroke` — referenced everywhere.
|
|
172
|
+
|
|
173
|
+
### C-49 Link treatment
|
|
174
|
+
The default for a link **inside running text** is colour **and** underline. Colour-blind users cannot separate a coloured link from surrounding prose; the underline is what makes it a link for them.
|
|
175
|
+
|
|
176
|
+
Two legitimate departures, and they are not the same:
|
|
177
|
+
|
|
178
|
+
| Case | Treatment | Why |
|
|
179
|
+
| --- | --- | --- |
|
|
180
|
+
| Body text link | Colour + underline | Default. Never drop both. |
|
|
181
|
+
| Already-obviously-interactive component — nav item, tab, card, button-styled link | Neither required | Position, container and grouping already signal it. Adding link styling clutters without informing. |
|
|
182
|
+
| Secondary link that colour + underline would over-weight | **Underline, no colour** | Keeps the affordance while restoring hierarchy — a supporting link should not compete with the primary action beside it. |
|
|
183
|
+
|
|
184
|
+
❌ Dropping the underline but keeping the colour, in body text. That is the one combination that fails colour-blind users while looking fine to everyone else.
|
|
185
|
+
✅ Keep the underline. Colour is the part you may drop — an underlined link without colour still reads as a link to everyone; a coloured link without an underline reads as a link only to people who can see the colour.
|
|
186
|
+
|
|
187
|
+
### C-50 Coloured heading text
|
|
188
|
+
❌ A heading tinted with the accent colour for emphasis
|
|
189
|
+
✅ Headings use `--color-text-strong`. Coloured text reads as interactive, so a coloured heading invites a click that does nothing. Emphasise with size, weight and space — the tools that already carry hierarchy.
|
|
190
|
+
|
|
191
|
+
### C-68 Non-interactive elements styled like interactive ones
|
|
192
|
+
❌ A "Verified" badge with the brand fill and the shape of the primary button; decorative icons carrying the same border and colour as a secondary button
|
|
193
|
+
✅ Things that look alike are expected to behave alike. If an element does nothing when clicked, it must not carry the visual signature of something that does — brand fill, button shape, or control border.
|
|
194
|
+
Differentiate deliberately: change the shape (a badge is more rounded than a button), the tone (`success` for a verified state rather than `brand`), and the emphasis (a `fill` background rather than a solid one, so the real primary action stays the most prominent thing on screen).
|
|
195
|
+
The converse also holds: two elements that do the same job should look the same.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## D. Spacing and layout
|
|
200
|
+
|
|
201
|
+
### D-23 Spacing off the scale
|
|
202
|
+
❌ An arbitrary margin or gap (13px, 7px) written at the call site
|
|
203
|
+
✅ Every spacing value comes from a `--spacing-*` token, all multiples of `--spacing-unit` (4px). An arbitrary value signals a missing token, not an exception.
|
|
204
|
+
|
|
205
|
+
### D-24 Symmetric spacing around headings
|
|
206
|
+
❌ Equal margin above and below a section heading
|
|
207
|
+
✅ A heading belongs to the content beneath it. Space above must exceed space below, usually by 2–3×. This one rule fixes more "it looks off and I can't say why" than any other in this file.
|
|
208
|
+
|
|
209
|
+
### D-69 Spacing that does not grow outward
|
|
210
|
+
❌ The same gap between a card's heading and its body, between cards, and between page sections
|
|
211
|
+
✅ An interface is rectangles inside rectangles. Spacing starts small at the innermost level and **increases as you move outward**:
|
|
212
|
+
|
|
213
|
+
| Level | Option |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| Text inside a component | XS (8) |
|
|
216
|
+
| Component padding, content within a section | M (24) |
|
|
217
|
+
| Between components, grid gutters | L (32) |
|
|
218
|
+
| Between page sections | XXL (80) |
|
|
219
|
+
|
|
220
|
+
`--spacing-group` → `--spacing-card` → `--grid-gutter` → `--spacing-section` already encode this per mode. When in doubt between two steps, take the larger one — tight spacing hides grouping and hierarchy, and generous spacing is the cheapest improvement available to any interface.
|
|
221
|
+
|
|
222
|
+
### D-96 Icon and text out of balance
|
|
223
|
+
❌ A large, heavy icon beside small, light label text — the icon shouts and the pair reads as two things
|
|
224
|
+
✅ Match the icon's weight and size to the text it accompanies. Where the icon set will not match, lower its **contrast** instead: `--color-stroke-strong` for the icon against `--color-text-weak` for the label. The pair should read as one unit.
|
|
225
|
+
|
|
226
|
+
### D-70 Broken left edge
|
|
227
|
+
❌ An icon sitting to the left of a heading while the body text below starts further left, so the text block has two left edges
|
|
228
|
+
✅ Keep one straight left edge down a block of related text. The eye returns to that edge on every line and every jump; breaking it costs a small amount of work on each one. Where an icon sits beside text, align the *text* edges and let the icon hang outside, or put the icon above.
|
|
229
|
+
|
|
230
|
+
### D-71 Multiple alignments in one component
|
|
231
|
+
❌ A centred name, a left-aligned quote, a right-aligned photo and centred stars in a single testimonial
|
|
232
|
+
✅ One alignment per component, start-aligned by default. Every additional alignment makes the eye zig-zag. Centring is acceptable for a short isolated block — a few lines of text and one action — but then centre *everything* in it, and make the action full-width so both hands can reach it.
|
|
233
|
+
|
|
234
|
+
### D-72 Mixed-size text centred vertically
|
|
235
|
+
❌ `$10` and `/month` on one line, vertically centred, so the small text floats
|
|
236
|
+
✅ Align text of different sizes to the **baseline**, not the vertical centre. The shared baseline connects them into one reading unit; vertical centring leaves the smaller item drifting in its own space.
|
|
237
|
+
|
|
238
|
+
### D-25 No proximity hierarchy
|
|
239
|
+
❌ Identical gaps between items within a group and between groups
|
|
240
|
+
✅ Related things sit closer together than unrelated things. If everything is evenly spaced, nothing is grouped.
|
|
241
|
+
|
|
242
|
+
### D-26 One padding value for all containers
|
|
243
|
+
❌ The same padding value on a badge, a card and a page section
|
|
244
|
+
✅ `--spacing-inline` for inline elements, `--spacing-card` for containers, `--spacing-section` for page sections. Padding scales with the container.
|
|
245
|
+
|
|
246
|
+
### D-27 Optical alignment ignored
|
|
247
|
+
❌ Icon and text label boxes aligned mathematically but reading as misaligned
|
|
248
|
+
✅ Align to what the eye sees. Icons frequently need a 1–2px nudge; circular shapes need slightly more size than square ones to look equal.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## E. States and interaction
|
|
253
|
+
|
|
254
|
+
Agents render the happy path. This section exists because that is the single most common gap in generated UI.
|
|
255
|
+
|
|
256
|
+
### E-28 Missing states
|
|
257
|
+
❌ A component with only a default appearance
|
|
258
|
+
✅ Every interactive element defines: `hover`, `focus-visible`, `active`, `disabled`. Every data view defines: loading, empty, error, and partial/truncated.
|
|
259
|
+
|
|
260
|
+
### E-29 Focus removed without replacement
|
|
261
|
+
❌ `outline: none` with nothing in its place
|
|
262
|
+
✅ A `:focus-visible` rule with a visible indicator built on `--color-focus`. Never remove the outline without replacing it. This locks out every keyboard user, and it is the most common accessibility failure in generated code.
|
|
263
|
+
|
|
264
|
+
### E-30 Empty states omitted
|
|
265
|
+
❌ A table that renders an empty `<tbody>` when there is no data
|
|
266
|
+
✅ Empty state says what would be here, why it isn't, and the one action that changes it. Distinguish "no data yet" from "no results for this filter" — they need different copy and different actions.
|
|
267
|
+
|
|
268
|
+
### E-31 Hover-only affordances
|
|
269
|
+
❌ Actions that appear only on `:hover`
|
|
270
|
+
✅ Visible on touch, or reachable via a persistent control. Half your traffic has no hover.
|
|
271
|
+
|
|
272
|
+
### E-32 Disabled buttons
|
|
273
|
+
❌ A greyed-out submit button with no indication of what's missing
|
|
274
|
+
✅ **Prefer not to disable at all.** Three better options, in order: enable it and validate on submit; remove the action and say why it is unavailable; or keep it at full contrast with a lock icon and explain how to unlock it.
|
|
275
|
+
A disabled button gives no feedback on press, usually fails contrast, and is skipped by keyboard focus — so the user cannot reach the element to discover why it is dead.
|
|
276
|
+
Where disabling is genuinely right, put a message beside it or a tooltip on it explaining what is needed, and **keep it keyboard-focusable** so assistive technology can reach that explanation. See `P-02`.
|
|
277
|
+
|
|
278
|
+
### E-33 `div` with a click handler
|
|
279
|
+
❌ A `div` or `span` carrying a click handler
|
|
280
|
+
✅ `<button type="button">` for actions, `<a href>` for navigation. If it performs an action it is a button; if it navigates it is an anchor. Keyboard operability and screen-reader semantics both come free with the correct element.
|
|
281
|
+
|
|
282
|
+
### E-34 Icon-only controls without names
|
|
283
|
+
❌ `<button><TrashIcon /></button>`
|
|
284
|
+
✅ `aria-label`, or visually-hidden text — and see `E-51`, which is a different problem. Verify the target is at least `--size-touch-target` (48px, unchanged in every mode) including padding.
|
|
285
|
+
|
|
286
|
+
### E-35 Dynamic results with no announcement
|
|
287
|
+
❌ A filtered count that updates silently
|
|
288
|
+
✅ `aria-live="polite"` on result counts and validation summaries.
|
|
289
|
+
|
|
290
|
+
### E-51 Icon without a visible label
|
|
291
|
+
❌ A toolbar of icon-only buttons whose meaning depends on recognising the glyph
|
|
292
|
+
✅ A visible text label beside the icon, or the icon plus label on the primary path.
|
|
293
|
+
This is **not** the same problem as `E-34`. An `aria-label` serves a screen reader and does nothing for a sighted user with low computer literacy, or anyone meeting an unfamiliar glyph. Both are required.
|
|
294
|
+
Icon-only is acceptable for a small set of near-universal glyphs (close, search, menu) and in `operator`, where repetition builds recognition — and even there, on first-run surfaces the label stays.
|
|
295
|
+
|
|
296
|
+
### E-52 Unconventional controls
|
|
297
|
+
❌ A bespoke form field, checkbox or select that looks and behaves unlike every other one the user has met
|
|
298
|
+
✅ Conventional shapes: inputs are rectangles with the label above, checkboxes are squares with a tick, radios are circles, links are underlined.
|
|
299
|
+
People arrive with a mental model built from every other product they use (Jakob's law). Matching it is free comprehension; departing from it charges the user to learn something that gains them nothing. Innovate on the product's actual purpose, not on its form fields.
|
|
300
|
+
|
|
301
|
+
### E-61 Important navigation hidden when it fits
|
|
302
|
+
❌ A hamburger menu on a viewport with room for three visible links
|
|
303
|
+
✅ Show what fits. People do not use what they cannot see, and every tap behind a menu is a tap some users will not make. Collapse only under genuine space pressure.
|
|
304
|
+
|
|
305
|
+
### E-62 Off-screen content with no affordance
|
|
306
|
+
❌ A horizontally scrolling row that ends flush at the viewport edge
|
|
307
|
+
✅ Expose the edge of the next item, or show an explicit control. If the user cannot tell there is more, there is not more.
|
|
308
|
+
|
|
309
|
+
### E-63 Minimal but unreadable
|
|
310
|
+
❌ Unlabelled icon navigation, a selected state signalled by a barely-different tint, primary actions hidden in an overflow menu, low-contrast icons
|
|
311
|
+
✅ Minimal is not the same as simple. A sparse interface that omits labels, states and actions is harder to use than a slightly busier one that names them.
|
|
312
|
+
The test is not how little is on screen. It is whether someone can tell what things are, which one is selected, and what they can do next. Remove decoration freely; never remove the answers to those three questions.
|
|
313
|
+
|
|
314
|
+
### E-64 Brand colour colliding with a system meaning
|
|
315
|
+
❌ A red brand colour used for links and primary buttons, on an interface that also uses red for errors and destructive actions
|
|
316
|
+
✅ Where the brand colour is red, amber or green, do **not** use it for interactive elements. Use `--color-text-strong` for links and buttons and keep the brand colour decorative.
|
|
317
|
+
One colour cannot mean both "act on this" and "something is wrong" without teaching the user that neither is reliable. The system colours have prior claim, because their meanings arrive with the user.
|
|
318
|
+
|
|
319
|
+
### E-65 More than one interactive colour
|
|
320
|
+
❌ A palette with three brand colours, all appearing on buttons and links
|
|
321
|
+
✅ **One** colour marks interactive elements — the highest-contrast one. Additional brand colours are decorative: backgrounds, borders, icons, illustration. A second interactive colour raises a question the user has to answer ("does this one do something different?") and there is no good answer.
|
|
322
|
+
|
|
323
|
+
### E-73 Interface built only for short content
|
|
324
|
+
❌ A layout tested with "Vite" and a two-word title, which breaks on a forty-character name or a four-digit count
|
|
325
|
+
✅ Design for the long case as well as the short one. Let content reflow, allow the component to grow, or reduce the type size — but do not clip data out of sight. Hidden overflow hides information the user may need.
|
|
326
|
+
|
|
327
|
+
### E-74 Truncating where items share a prefix
|
|
328
|
+
❌ "User Interface Design Fundamentals C…" repeated down a list, every row identical
|
|
329
|
+
✅ Where truncation is unavoidable and items share a leading string, **crop in the middle**: "User Interface De…Chapter 2 – Typography". Truncation must preserve the part that tells items apart, which is rarely the beginning.
|
|
330
|
+
Better still, remove the shared prefix entirely and put it in a heading (`A-59`).
|
|
331
|
+
|
|
332
|
+
### E-91 Button hierarchy carried by colour alone
|
|
333
|
+
❌ A blue primary beside a green secondary, identical in every other respect
|
|
334
|
+
✅ Weights differ in **structure** — solid fill, outline, underlined text — not just hue. Two buttons differing only in colour are the same button to a colour-blind user, and if their contrast against each other is under 3:1 they are the same button to a low-vision user too.
|
|
335
|
+
This also means a button's fill or border is not decorative: it is the thing identifying the element as a button, so it carries the 3:1 non-text floor.
|
|
336
|
+
|
|
337
|
+
### E-92 Light grey secondary button
|
|
338
|
+
❌ A pale grey filled or outlined button beside the primary
|
|
339
|
+
✅ Grey reads as **disabled**, so users skip it. Its text and border rarely clear 4.5:1 and 3:1 either. Use an outlined button in `--color-brand`.
|
|
340
|
+
|
|
341
|
+
### E-93 Inconsistent button shapes
|
|
342
|
+
❌ A pill-shaped primary next to a rounded-rectangle secondary
|
|
343
|
+
✅ One shape across all weights. Different shapes imply different behaviour; if the behaviour is the same, the difference is noise the user has to resolve.
|
|
344
|
+
|
|
345
|
+
### E-94 Destructive actions coloured red at rest
|
|
346
|
+
❌ A red "Delete" sitting in a list of rows
|
|
347
|
+
✅ At rest a destructive action is **tertiary** — less prominent, further from the primary, or disclosed. Red makes it *more* prominent, which is backwards: the goal is friction, not attention.
|
|
348
|
+
`--color-danger` styling belongs on the **confirming** button inside the confirmation step, where the user has already chosen and needs to feel the weight of it.
|
|
349
|
+
|
|
350
|
+
### E-95 Primary action parked at the right
|
|
351
|
+
❌ A right-aligned "Next" with "Back" beside it at the bottom of a multi-step form
|
|
352
|
+
✅ Start-align the primary, ordered most to least important. Right-aligned actions get missed on wide screens and by screen-magnifier users, and sit further from the fields they submit.
|
|
353
|
+
On multi-step forms put **"Back" as a tertiary button at the top left** — away from the primary, where it cannot be hit by mistake and lose everything just entered.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## F. Forms
|
|
358
|
+
|
|
359
|
+
### F-36 Placeholder as label
|
|
360
|
+
❌ `<input placeholder="Email address">`
|
|
361
|
+
✅ Persistent `<label>`. Placeholders vanish on focus, fail low-vision users, and break autofill heuristics.
|
|
362
|
+
|
|
363
|
+
### F-37 Unhelpful error text
|
|
364
|
+
❌ "Invalid input", "Error", "This field is required"
|
|
365
|
+
✅ Say what is wrong and what to do: "Enter a date after today." Errors are instructions, not verdicts.
|
|
366
|
+
|
|
367
|
+
### F-38 Validation timing wrong at both ends
|
|
368
|
+
❌ Validating on every keystroke, or only on submit with no inline recovery
|
|
369
|
+
✅ Validate on blur, revalidate on change once a field has errored, summarise on submit.
|
|
370
|
+
|
|
371
|
+
### F-39 Input cleared on error
|
|
372
|
+
❌ Re-rendering the form empty after a failed submit
|
|
373
|
+
✅ Echo every submitted value back into the field. Losing a user's typing is the most expensive small bug in forms.
|
|
374
|
+
|
|
375
|
+
### F-40 Missing input affordances
|
|
376
|
+
❌ `<input type="text">` for email, phone, numbers, one-time codes
|
|
377
|
+
✅ Correct `type`, `inputmode`, `autocomplete`, and `enterkeyhint`. On mobile this is the difference between a usable form and an abandoned one.
|
|
378
|
+
|
|
379
|
+
### F-41 Critical function dependent on JavaScript
|
|
380
|
+
❌ A form that only submits via `fetch`, a nav that only opens with JS
|
|
381
|
+
✅ Works without JS, enhanced with it. On an unreliable mobile connection, JS-dependency is not a hypothetical — it is a silent failure that looks to the user like nothing happened.
|
|
382
|
+
|
|
383
|
+
### F-97 Required fields left unmarked
|
|
384
|
+
❌ "All fields are required unless marked optional" at the top, with only optional fields marked
|
|
385
|
+
✅ Mark **both**. Required takes `*` (with the convention stated at the top) or the word "required"; optional takes the word "optional".
|
|
386
|
+
Instructions at the top of a form get scanned past, leaving people guessing field by field. Marking both is also an accessibility requirement for screen reader users, so sighted users may as well get the same clarity. **Never colour the asterisk red** — red means error.
|
|
387
|
+
Unmarked required fields are acceptable only where: the product has no optional fields at all; the form is short and familiar (login, newsletter); one question is asked per screen with its reason given; or testing has shown the markers unnecessary.
|
|
388
|
+
|
|
389
|
+
### F-98 Labels beside inputs
|
|
390
|
+
❌ Labels to the left of their fields, left- or right-aligned
|
|
391
|
+
✅ Stack the label directly above its input, `--spacing-label` (4px) away.
|
|
392
|
+
Left-placed labels make the eye zig-zag between column and field. Right-aligning them to shorten that distance creates a jagged left edge that is harder to scan. Long labels wrap awkwardly in the narrow column. Stacked above and close, label and input are read in one focus.
|
|
393
|
+
|
|
394
|
+
### F-99 Uniform field widths
|
|
395
|
+
❌ Every field full-width, including a four-digit postcode and a three-digit CVC
|
|
396
|
+
✅ Width should match the expected input. It is the strongest signal people have about how much is wanted, and a wide box for a short answer creates hesitation. Where length varies, size for the common case.
|
|
397
|
+
|
|
398
|
+
### F-100 A dropdown for a small set of options
|
|
399
|
+
❌ A select holding three, five or eight choices
|
|
400
|
+
✅ Radio buttons, stacked vertically, for roughly ten options or fewer. A dropdown costs open, scroll and choose — several precise interactions — hides its options from scanning and comparison, and looks filled when empty, so it gets skipped. Radios cost one press and stay visible.
|
|
401
|
+
|
|
402
|
+
### F-101 A long dropdown where autocomplete belongs
|
|
403
|
+
❌ A 200-entry country select
|
|
404
|
+
✅ An autocomplete field when people already know the answer. Keep suggestions to about ten and **bold the differing part** so they can be told apart quickly.
|
|
405
|
+
Where people must *browse* to decide, split the list into two dependent fields — industry then occupation — rather than one enormous one.
|
|
406
|
+
|
|
407
|
+
### F-102 Toggle and checkbox used interchangeably
|
|
408
|
+
❌ A toggle inside a form that only applies on submit
|
|
409
|
+
✅ The distinction is **when the change takes effect**. A checkbox waits for a submit button; a toggle applies immediately. Label both with what happens when they are **on**.
|
|
410
|
+
|
|
411
|
+
### F-103 Negatively phrased checkbox labels
|
|
412
|
+
❌ "Don't allow automatic updates"
|
|
413
|
+
✅ Describe what happens when it is **checked**. Test by prefixing "Yes,": "Yes, allow automatic updates" reads cleanly; "Yes, don't allow automatic updates" does not.
|
|
414
|
+
|
|
415
|
+
### F-104 Instructional verbs in labels
|
|
416
|
+
❌ "Enter your email", "Type your email here"
|
|
417
|
+
✅ "Email". The input field already tells people to type in it.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## G. Motion
|
|
422
|
+
|
|
423
|
+
### G-42 Entrance animation on everything
|
|
424
|
+
❌ Every section fading and rising on scroll
|
|
425
|
+
✅ Motion earns its place by explaining a change of state or spatial relationship. Decoration on a page the user will visit twice a day becomes friction.
|
|
426
|
+
|
|
427
|
+
### G-43 `prefers-reduced-motion` ignored
|
|
428
|
+
❌ Animation with no reduced-motion path
|
|
429
|
+
✅ Always provide the reduced path. Non-negotiable — this is a vestibular safety issue, not a preference.
|
|
430
|
+
|
|
431
|
+
### G-44 Durations too long
|
|
432
|
+
❌ 500ms+ on UI feedback
|
|
433
|
+
✅ 100–200ms for state change, up to 300ms for larger transitions. If it can be perceived as waiting, it is too slow.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## H. Code-level
|
|
438
|
+
|
|
439
|
+
### H-45 New component instead of the existing one
|
|
440
|
+
❌ Writing a fresh button component because the repo's was not read
|
|
441
|
+
✅ Search the codebase first. Extend what exists. Duplication here is where design systems die.
|
|
442
|
+
|
|
443
|
+
### H-46 Local convention overridden
|
|
444
|
+
❌ Introducing a second styling approach, naming scheme or file layout
|
|
445
|
+
✅ Match the surrounding file. Consistency with the codebase outranks personal preference and outranks this document.
|
|
446
|
+
|
|
447
|
+
### H-47 Values hard-coded past the token layer
|
|
448
|
+
❌ A raw hex colour or pixel size written in component code
|
|
449
|
+
✅ Reference the token. Consume the semantic role (`--color-text-strong`), not the primitive (`--color-neutral-900`). A value that cannot be expressed as a token indicates a missing token.
|
|
450
|
+
|
|
451
|
+
### H-48 JavaScript for something CSS does
|
|
452
|
+
❌ Scroll listeners for sticky positioning; scripted accordions and dialogs that have native equivalents
|
|
453
|
+
✅ Platform first: `position: sticky`, `<details>`, `<dialog>`, `:has()`, container queries, `scroll-behavior`, `popover`. Reach for a framework when the platform genuinely lacks the capability.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## I. Copy
|
|
458
|
+
|
|
459
|
+
**Moved to `05-copy.md`.** Interface text outgrew a section here. Rules `I-53` through `I-57` kept their numbers and live in that file, alongside the rest of the copy rules.
|
|
460
|
+
|
|
461
|
+
Load `05-copy.md` whenever writing or reviewing a user-facing string.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Self-check before finishing
|
|
466
|
+
|
|
467
|
+
Run this against what you produced. Any "no" is a defect to fix, not a note to mention.
|
|
468
|
+
|
|
469
|
+
1. Would this look different from a generic template if the accent colour were removed? (A-01 → A-10)
|
|
470
|
+
2. Is every run of prose measure-capped and left-aligned? (B-11, B-12)
|
|
471
|
+
3. Does any text or placeholder fall below 4.5:1? (C-19)
|
|
472
|
+
4. Is every spacing value a `--spacing-*` token, and does `--spacing-heading-before` exceed `--spacing-heading-after`? (D-23, D-24)
|
|
473
|
+
5. Does every interactive element have hover, focus-visible, active and disabled? (E-28)
|
|
474
|
+
5b. Are links underlined, headings uncoloured, and icon-only controls labelled? (C-49, C-50, E-51)
|
|
475
|
+
5c. Do control borders use `--color-stroke-strong` and clear 3:1? (`02-tokens.md`)
|
|
476
|
+
5d. Has the copy been checked against `05-copy.md`? (sentence case, front-loaded, plain, descriptive links, actionable errors)
|
|
477
|
+
5e. Can a user tell what each control is, which item is selected, and what to do next? (E-63)
|
|
478
|
+
5f. Does spacing grow from the innermost rectangle outward, and does the component use one alignment? (D-69, D-71)
|
|
479
|
+
5g. Does the layout survive the longest realistic content, and does nothing non-interactive look interactive? (E-73, C-68)
|
|
480
|
+
5i. Do button weights differ in structure rather than colour, is there one primary, is the tertiary underlined, and are destructive actions low-prominence? (E-91, P-02, E-94)
|
|
481
|
+
5h. Is long-form text 18px+ at 1.5+, start-aligned, 40–80 characters per line, in one typeface and two weights? (B-75, B-12, B-76, B-77)
|
|
482
|
+
5j. Single column, labels above and close, both required and optional marked, hints above the input, widths matched, borders at 3:1? (P-04, F-98, F-97, P-03, F-99)
|
|
483
|
+
6. Does `outline-none` appear anywhere without a replacement indicator? (E-29)
|
|
484
|
+
7. Does every data view have a loading, empty and error state? (E-28, E-30)
|
|
485
|
+
8. Does every form field have a persistent label, correct `type`/`inputmode`/`autocomplete`, and value echo on error? (F-36, F-39, F-40)
|
|
486
|
+
9. Does the primary action still work with JavaScript disabled? (F-41)
|
|
487
|
+
10. Is there a `prefers-reduced-motion` path? (G-43)
|
|
488
|
+
11. Did you reuse existing components and tokens rather than adding new ones? (H-45, H-47)
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Notes for the author (not for the agent)
|
|
493
|
+
|
|
494
|
+
Rules carrying a deliberate house position rather than a general best practice — these are where your taste is recorded, and they should be defended or changed consciously:
|
|
495
|
+
|
|
496
|
+
- **C-18** — off-white over pure white. Already evidenced in your site's `#fafaf7`, now the anchor of the default neutral ramp.
|
|
497
|
+
- **A-07, A-08** — bordered, low-radius, low-shadow surfaces. This is a stance, not a consensus.
|
|
498
|
+
- **F-41, G-42** — resilience and restraint weighted above visual richness. Connects directly to your writing on JS-dependent form fields.
|
|
499
|
+
- **D-24** — heading space asymmetry. Universal advice, but stating a ratio makes it enforceable.
|
|
500
|
+
|
|
501
|
+
Candidates deferred to mode profiles because they are not universal: information density, table row height, use of colour fills for status, page-section rhythm, hero presence, illustration and imagery policy, animation budget.
|