@mohammadhprp/system-prompt 0.12.2 → 0.12.4
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/framework/commands/README.md +1 -1
- package/framework/commands/review.md +1 -1
- package/framework/mcps/README.md +0 -1
- package/framework/plugins/ponytail/README.md +0 -1
- package/framework/plugins/ponytail/capabilities.md +1 -1
- package/framework/references/standards/api.md +0 -1
- package/framework/references/standards/architecture.md +0 -1
- package/framework/references/standards/database.md +0 -1
- package/framework/references/standards/debugging.md +0 -1
- package/framework/references/standards/documentation.md +0 -1
- package/framework/references/standards/logging.md +0 -1
- package/framework/references/standards/naming.md +0 -1
- package/framework/references/standards/observability.md +0 -1
- package/framework/references/standards/performance.md +0 -1
- package/framework/references/standards/pull-requests.md +0 -1
- package/framework/references/standards/security.md +0 -1
- package/framework/references/standards/testing.md +0 -1
- package/framework/skills/README.md +16 -12
- package/framework/skills/architect/SKILL.md +83 -0
- package/framework/skills/architect/examples.md +5 -0
- package/framework/skills/architect/references/design-red-flags.md +33 -0
- package/framework/skills/architect/references/rationale-template.md +35 -0
- package/framework/skills/architect/references/runner-prompt.md +20 -0
- package/framework/skills/arena/SKILL.md +71 -0
- package/framework/skills/arena/examples.md +5 -0
- package/framework/skills/bro/SKILL.md +7 -0
- package/framework/skills/bro/examples.md +5 -0
- package/framework/skills/changelog/SKILL.md +41 -0
- package/framework/skills/changelog/examples.md +5 -0
- package/framework/skills/commit/SKILL.md +28 -0
- package/framework/skills/commit/examples.md +5 -0
- package/framework/skills/how/SKILL.md +135 -0
- package/framework/skills/how/examples.md +5 -0
- package/framework/skills/how/references/critic-prompt.md +59 -0
- package/framework/skills/how/references/critique-rubric.md +58 -0
- package/framework/skills/how/references/explainer-prompt.md +55 -0
- package/framework/skills/how/references/explorer-prompt.md +52 -0
- package/framework/skills/improve/SKILL.md +1 -3
- package/framework/skills/improve/examples.md +1 -1
- package/framework/skills/laravel-best-practices/SKILL.md +56 -216
- package/framework/skills/laravel-best-practices/references/architecture.md +1 -1
- package/framework/skills/laravel-best-practices/references/checklist.md +139 -0
- package/framework/skills/laravel-best-practices/references/eloquent.md +4 -2
- package/framework/skills/laravel-best-practices/references/infer-conventions.md +97 -0
- package/framework/skills/laravel-best-practices/references/pest-testing.md +47 -18
- package/framework/skills/laravel-best-practices/references/security.md +1 -1
- package/framework/skills/laravel-best-practices/references/style.md +1 -1
- package/framework/skills/laravel-best-practices/references/tailwindcss.md +0 -1
- package/framework/skills/merge-request/SKILL.md +40 -0
- package/framework/skills/merge-request/examples.md +5 -0
- package/framework/skills/pull-request/SKILL.md +31 -0
- package/framework/skills/pull-request/examples.md +5 -0
- package/framework/skills/release/SKILL.md +30 -0
- package/framework/skills/release/examples.md +5 -0
- package/framework/skills/review/SKILL.md +18 -0
- package/framework/skills/review/examples.md +5 -0
- package/framework/skills/tdd/SKILL.md +44 -0
- package/framework/skills/tdd/examples.md +5 -0
- package/framework/skills/unslop/SKILL.md +81 -0
- package/framework/skills/unslop/examples.md +5 -0
- package/framework/skills/why/SKILL.md +230 -0
- package/framework/skills/why/examples.md +5 -0
- package/framework/skills/why/references/epistemics.md +144 -0
- package/framework/skills/why/references/investigator-prompt.md +103 -0
- package/framework/skills/why/references/source-playbook.md +17 -0
- package/framework/skills/why/references/sources/code-archaeology.md +88 -0
- package/framework/skills/why/references/sources/databricks.md +70 -0
- package/framework/skills/why/references/sources/datadog.md +99 -0
- package/framework/skills/why/references/sources/incident-postmortem.md +15 -0
- package/framework/skills/why/references/sources/linear.md +48 -0
- package/framework/skills/why/references/sources/notion.md +55 -0
- package/framework/skills/why/references/sources/sentry.md +100 -0
- package/framework/skills/why/references/sources/slack.md +54 -0
- package/framework/skills/why/references/synthesizer-prompt.md +135 -0
- package/package.json +1 -1
- package/src/catalog.js +13 -12
- package/framework/mcps/notion-mcp/README.md +0 -57
- package/framework/mcps/notion-mcp/capabilities.md +0 -118
- package/framework/mcps/notion-mcp/configs/opencode.json +0 -9
- package/framework/mcps/notion-mcp/install.md +0 -70
- package/framework/mcps/notion-mcp/troubleshooting.md +0 -95
- package/framework/skills/brainstorming/SKILL.md +0 -159
- package/framework/skills/brainstorming/examples.md +0 -49
- package/framework/skills/brainstorming/scripts/frame-template.html +0 -213
- package/framework/skills/brainstorming/scripts/helper.js +0 -167
- package/framework/skills/brainstorming/scripts/server.cjs +0 -723
- package/framework/skills/brainstorming/scripts/start-server.sh +0 -209
- package/framework/skills/brainstorming/scripts/stop-server.sh +0 -120
- package/framework/skills/brainstorming/visual-companion.md +0 -291
- package/framework/skills/code-review/SKILL.md +0 -72
- package/framework/skills/code-review/examples.md +0 -31
- package/framework/skills/codenavi/SKILL.md +0 -306
- package/framework/skills/codenavi/examples.md +0 -33
- package/framework/skills/codenavi/references/coding-principles.md +0 -143
- package/framework/skills/codenavi/references/notebook-spec.md +0 -171
- package/framework/skills/create-adr/SKILL.md +0 -429
- package/framework/skills/create-adr/examples.md +0 -35
- package/framework/skills/design/SKILL.md +0 -399
- package/framework/skills/design/examples.md +0 -34
- package/framework/skills/great-interface/SKILL.md +0 -43
- package/framework/skills/great-interface/references/animations.md +0 -205
- package/framework/skills/great-interface/references/better-accessibility.md +0 -106
- package/framework/skills/great-interface/references/better-colors.md +0 -100
- package/framework/skills/great-interface/references/better-interface.md +0 -132
- package/framework/skills/great-interface/references/better-layout.md +0 -76
- package/framework/skills/great-interface/references/better-typography.md +0 -157
- package/framework/skills/great-interface/references/better-ui.md +0 -107
- package/framework/skills/great-interface/references/better-writing.md +0 -109
- package/framework/skills/great-interface/references/choosing-fonts.md +0 -64
- package/framework/skills/great-interface/references/color-formats.md +0 -90
- package/framework/skills/great-interface/references/color-usage.md +0 -118
- package/framework/skills/great-interface/references/contrast.md +0 -79
- package/framework/skills/great-interface/references/css-cheat-sheet.md +0 -65
- package/framework/skills/great-interface/references/details-and-accessibility.md +0 -119
- package/framework/skills/great-interface/references/enter-exit.md +0 -147
- package/framework/skills/great-interface/references/explain-interface.md +0 -126
- package/framework/skills/great-interface/references/find-the-effect.md +0 -94
- package/framework/skills/great-interface/references/focus-and-keyboard.md +0 -131
- package/framework/skills/great-interface/references/forms.md +0 -84
- package/framework/skills/great-interface/references/from-an-image.md +0 -55
- package/framework/skills/great-interface/references/grouping-and-alignment.md +0 -123
- package/framework/skills/great-interface/references/hit-areas.md +0 -94
- package/framework/skills/great-interface/references/icon-transitions.md +0 -102
- package/framework/skills/great-interface/references/icons.md +0 -110
- package/framework/skills/great-interface/references/interface-review.md +0 -148
- package/framework/skills/great-interface/references/motion-and-zoom.md +0 -79
- package/framework/skills/great-interface/references/no-browser.md +0 -73
- package/framework/skills/great-interface/references/palette-generation.md +0 -104
- package/framework/skills/great-interface/references/palette-structure.md +0 -76
- package/framework/skills/great-interface/references/performance.md +0 -88
- package/framework/skills/great-interface/references/picker.md +0 -76
- package/framework/skills/great-interface/references/read-the-system.md +0 -178
- package/framework/skills/great-interface/references/removed-signals.md +0 -38
- package/framework/skills/great-interface/references/review-format.md +0 -46
- package/framework/skills/great-interface/references/scope-resolution.md +0 -88
- package/framework/skills/great-interface/references/screen-readers.md +0 -101
- package/framework/skills/great-interface/references/semantics-and-aria.md +0 -84
- package/framework/skills/great-interface/references/spacing-and-adaptivity.md +0 -159
- package/framework/skills/great-interface/references/spacing-and-sizing.md +0 -121
- package/framework/skills/great-interface/references/surfaces.md +0 -219
- package/framework/skills/great-interface/references/token-naming.md +0 -97
- package/framework/skills/great-interface/references/variable-fonts-and-opentype.md +0 -105
- package/framework/skills/great-interface/references/variant.md +0 -104
- package/framework/skills/great-interface/references/wrapping-and-punctuation.md +0 -55
- package/framework/skills/learning-opportunities/SKILL.md +0 -140
- package/framework/skills/learning-opportunities/examples.md +0 -34
- package/framework/skills/learning-opportunities/references/PRINCIPLES.md +0 -42
- package/framework/skills/notion-mcp/SKILL.md +0 -57
- package/framework/skills/notion-mcp/examples.md +0 -31
- package/framework/skills/notion-mcp/references/comments.md +0 -112
- package/framework/skills/notion-mcp/references/databases.md +0 -193
- package/framework/skills/notion-mcp/references/pages.md +0 -235
- package/framework/skills/spec-driven-eval/SKILL.md +0 -341
- package/framework/skills/spec-driven-eval/examples.md +0 -35
- package/framework/skills/spec-driven-eval/references/quickstart.md +0 -118
- package/framework/skills/spec-driven-eval/references/reference.md +0 -295
- package/framework/skills/technical-design-doc-creator/README.md +0 -411
- package/framework/skills/technical-design-doc-creator/SKILL.md +0 -1484
- package/framework/skills/technical-design-doc-creator/examples.md +0 -35
- package/framework/skills/web-design-guidelines/SKILL.md +0 -65
- package/framework/skills/web-design-guidelines/examples.md +0 -32
- package/framework/skills/web-design-guidelines/references/guideline.md +0 -174
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
# Reading the whole system
|
|
2
|
-
|
|
3
|
-
Use this where no specific effect was named and the question is how the interface is built in general. For one named thing, use [find-the-effect.md](find-the-effect.md).
|
|
4
|
-
|
|
5
|
-
Every snippet runs in the page context, through whatever browser tooling is available: `evaluate_script` in the Chrome DevTools MCP, the console, a Playwright `page.evaluate`. Each returns data rather than printing, so the result comes back whole.
|
|
6
|
-
|
|
7
|
-
Run them in this order. Tokens first, because a page that hands you its custom properties has already told you most of the answer.
|
|
8
|
-
|
|
9
|
-
## The gotcha that costs you the run
|
|
10
|
-
|
|
11
|
-
`sheet.cssRules` throws on a cross-origin stylesheet. Every snippet that walks stylesheets wraps the access and reports what it could not read. An explanation that silently skipped the main stylesheet describes a page nobody is looking at.
|
|
12
|
-
|
|
13
|
-
## The stack first
|
|
14
|
-
|
|
15
|
-
"How was this site built" wants the frontend named before a type scale. Run this, then report each hit with its evidence, never as a bare claim:
|
|
16
|
-
|
|
17
|
-
```js
|
|
18
|
-
const html = document.documentElement;
|
|
19
|
-
const res = performance.getEntriesByType('resource').map(r => r.name);
|
|
20
|
-
const any = re => res.some(n => re.test(n));
|
|
21
|
-
const attr = sel => !!document.querySelector(sel);
|
|
22
|
-
({
|
|
23
|
-
framework: {
|
|
24
|
-
next: !!window.__NEXT_DATA__ || any(/\/_next\/static/),
|
|
25
|
-
nextAppRouter: typeof self.__next_f !== 'undefined',
|
|
26
|
-
nuxt: !!window.__NUXT__ || any(/\/_nuxt\//),
|
|
27
|
-
remix: !!window.__remixContext,
|
|
28
|
-
gatsby: !!window.___gatsby,
|
|
29
|
-
astro: attr('astro-island, [data-astro-cid]'),
|
|
30
|
-
svelte: attr('[class*="svelte-"]') || any(/\/_app\/immutable\//),
|
|
31
|
-
angular: attr('[ng-version]'),
|
|
32
|
-
reactFiber: Object.keys(document.body.firstElementChild ?? {}).some(k => k.startsWith('__react')),
|
|
33
|
-
},
|
|
34
|
-
styling: {
|
|
35
|
-
tailwind: getComputedStyle(html).getPropertyValue('--tw-ring-offset-width') !== ''
|
|
36
|
-
|| !!document.querySelector('[class*="bg-linear-to"], [class*="bg-gradient-to"]'),
|
|
37
|
-
tailwindV4: !!document.querySelector('[class*="bg-linear-to"]'),
|
|
38
|
-
cssModules: attr('[class*="_"][class*="__"]'),
|
|
39
|
-
styledComponents: attr('[class^="sc-"]') || attr('style[data-styled]'),
|
|
40
|
-
emotion: attr('[class^="css-"]'),
|
|
41
|
-
},
|
|
42
|
-
components: {
|
|
43
|
-
radix: attr('[data-radix-popper-content-wrapper], [data-radix-scroll-area-viewport]')
|
|
44
|
-
|| !!document.querySelector('[data-slot], [data-state][data-side]'),
|
|
45
|
-
baseUi: attr('[data-base-ui-portal], [class*="base-ui"]'),
|
|
46
|
-
headlessUi: attr('[data-headlessui-state]'),
|
|
47
|
-
mui: attr('[class*="Mui"]'),
|
|
48
|
-
arkOrChakra: attr('[data-scope][data-part]'),
|
|
49
|
-
},
|
|
50
|
-
motion: { animationsRunning: document.getAnimations().length, gsap: !!window.gsap },
|
|
51
|
-
images: { nextImage: any(/\/_next\/image\?/), modernFormats: [...document.images].some(i => /\.(avif|webp)/.test(i.currentSrc)), srcset: [...document.images].filter(i => i.srcset).length },
|
|
52
|
-
fonts: { count: document.fonts.size, variable: [...document.fonts].some(f => String(f.weight).includes(' ')), selfHosted: !any(/fonts\.g(oogleapis|static)\.com/) },
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Two rules. A fingerprint is not a fact, so give the evidence: `/_next/static` in an asset path is strong, a utility-looking class name alone is weak. And a `false` is not an absence, only a fingerprint that did not fire.
|
|
57
|
-
|
|
58
|
-
## Tokens
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
const tokens = {}; const unreadable = [];
|
|
62
|
-
for (const sheet of document.styleSheets) {
|
|
63
|
-
let rules; try { rules = sheet.cssRules } catch { unreadable.push(sheet.href); continue }
|
|
64
|
-
for (const r of rules ?? []) {
|
|
65
|
-
if (r.selectorText === ':root' || r.selectorText === 'html') {
|
|
66
|
-
for (const prop of r.style) {
|
|
67
|
-
if (prop.startsWith('--')) tokens[prop] = r.style.getPropertyValue(prop).trim();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
({ tokens, unreadable, count: Object.keys(tokens).length });
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Group the result by prefix. The prefixes are the system's own layer names, and a two-tier structure, `--blue-500` feeding `--color-text-primary`, is the seam `better-colors` calls the semantic tier.
|
|
76
|
-
|
|
77
|
-
## The type scale
|
|
78
|
-
|
|
79
|
-
Leaf text nodes only, so a wrapper's inherited size is not counted as its own step.
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
const seen = new Map();
|
|
83
|
-
for (const el of document.querySelectorAll('*')) {
|
|
84
|
-
if (el.children.length || !el.textContent?.trim()) continue;
|
|
85
|
-
const s = getComputedStyle(el);
|
|
86
|
-
const key = `${parseFloat(s.fontSize)}px w${s.fontWeight} lh ${s.lineHeight} ls ${s.letterSpacing}`;
|
|
87
|
-
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
88
|
-
}
|
|
89
|
-
[...seen].sort((a, b) => b[1] - a[1]);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Sorted by usage, so the body size is first and the one-offs last. Derive the ratio between adjacent sizes. A consistent ratio means a scale; scattered values mean hard-coded sizes.
|
|
93
|
-
|
|
94
|
-
## The spacing rhythm
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
const vals = new Map();
|
|
98
|
-
for (const el of document.querySelectorAll('*')) {
|
|
99
|
-
const s = getComputedStyle(el);
|
|
100
|
-
for (const p of ['paddingTop', 'paddingLeft', 'marginTop', 'marginLeft', 'gap', 'rowGap']) {
|
|
101
|
-
const v = parseFloat(s[p]);
|
|
102
|
-
if (v > 0) vals.set(v, (vals.get(v) ?? 0) + 1);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
[...vals].sort((a, b) => a[0] - b[0]);
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Look for the base unit that divides most values, then check `better-layout`'s grouping rule. Is the gap between groups at least 2× the gap within one?
|
|
109
|
-
|
|
110
|
-
## Radii, shadows, borders
|
|
111
|
-
|
|
112
|
-
```js
|
|
113
|
-
const grab = (prop, skip) => {
|
|
114
|
-
const m = new Map();
|
|
115
|
-
for (const el of document.querySelectorAll('*')) {
|
|
116
|
-
const v = getComputedStyle(el)[prop];
|
|
117
|
-
if (v && v !== skip) m.set(v, (m.get(v) ?? 0) + 1);
|
|
118
|
-
}
|
|
119
|
-
return [...m].sort((a, b) => b[1] - a[1]);
|
|
120
|
-
};
|
|
121
|
-
({ radius: grab('borderRadius', '0px'), shadow: grab('boxShadow', 'none') });
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Count the distinct shadows. One or two recipes is a system; nine is a page where everyone invented their own elevation.
|
|
125
|
-
|
|
126
|
-
## Motion
|
|
127
|
-
|
|
128
|
-
```js
|
|
129
|
-
const t = new Map();
|
|
130
|
-
for (const el of document.querySelectorAll('*')) {
|
|
131
|
-
const s = getComputedStyle(el);
|
|
132
|
-
if (s.transitionDuration === '0s') continue;
|
|
133
|
-
const key = `${s.transitionProperty} ${s.transitionDuration} ${s.transitionTimingFunction}`;
|
|
134
|
-
t.set(key, (t.get(key) ?? 0) + 1);
|
|
135
|
-
}
|
|
136
|
-
[...t].sort((a, b) => b[1] - a[1]);
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
`transition: all` shows up here as `all`. Custom curves arrive as `cubic-bezier(...)`, and the built-in keywords tell you nobody tuned them.
|
|
140
|
-
|
|
141
|
-
## Breakpoints
|
|
142
|
-
|
|
143
|
-
```js
|
|
144
|
-
const bp = new Set(); const unreadable = [];
|
|
145
|
-
for (const sheet of document.styleSheets) {
|
|
146
|
-
let rules; try { rules = sheet.cssRules } catch { unreadable.push(sheet.href); continue }
|
|
147
|
-
const walk = list => { for (const r of list ?? []) {
|
|
148
|
-
if (r.media) { for (const m of r.media) { const hit = m.match(/(min|max)-width:\s*([\d.]+)(px|r?em)/); if (hit) bp.add(hit[0]) } }
|
|
149
|
-
if (r.cssRules) walk(r.cssRules);
|
|
150
|
-
}};
|
|
151
|
-
walk(rules);
|
|
152
|
-
}
|
|
153
|
-
({ breakpoints: [...bp].sort(), unreadable });
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Compare against the framework defaults. Breakpoints at exactly `640/768/1024/1280` are Tailwind's out of the box, which tells you they were never chosen.
|
|
157
|
-
|
|
158
|
-
## Fonts and theming
|
|
159
|
-
|
|
160
|
-
```js
|
|
161
|
-
({
|
|
162
|
-
loaded: [...document.fonts].map(f => `${f.family} ${f.weight} ${f.style} ${f.status}`),
|
|
163
|
-
bodyStack: getComputedStyle(document.body).fontFamily,
|
|
164
|
-
variable: [...document.fonts].some(f => String(f.weight).includes(' ')),
|
|
165
|
-
themeClass: document.documentElement.className || '(none)',
|
|
166
|
-
colorScheme: getComputedStyle(document.documentElement).colorScheme,
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
`variable: true` means one file covers a weight range. A class on `<html>` beside a `prefers-color-scheme` query means a toggle that can override the system, the mechanism `better-colors` describes.
|
|
171
|
-
|
|
172
|
-
## Reading a second state
|
|
173
|
-
|
|
174
|
-
Everything above reads one state at one width. Before writing the explanation, at minimum:
|
|
175
|
-
|
|
176
|
-
- Resize to 375px and re-run the spacing and breakpoint snippets. The values that change are what is fluid.
|
|
177
|
-
- Toggle the theme and re-run the token snippet. The tokens that change are the themed layer, the ones that do not are the primitives.
|
|
178
|
-
- Tab to the first interactive control and read its `:focus-visible` styles, since a focus ring is one of the most common absences.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Removed signals
|
|
2
|
-
|
|
3
|
-
What to look for on the `-` side of a hunk and which skill owns the judgement. A row here is a lead, never a finding. Route the removal to its owner and report it only once that skill confirms the interface got worse.
|
|
4
|
-
|
|
5
|
-
| Removed from the `-` side | Owner | What to check |
|
|
6
|
-
| --- | --- | --- |
|
|
7
|
-
| `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-live`, `role=` | `better-accessibility` | The control or region lost its accessible name, description, or announcement |
|
|
8
|
-
| `alt=`, `<label`, `for=`, `scope=` | `better-accessibility` | Image, field, or table cell lost its programmatic association |
|
|
9
|
-
| `<button>`, `<a>`, `<nav>`, `<main>`, `<ul>` replaced by `div` or `span` | `better-accessibility` | Keyboard and assistive-technology behavior was traded for styling |
|
|
10
|
-
| `:focus-visible`, `:focus`, `outline`, `tabindex` | `better-accessibility` | Keyboard users lost the focus indicator or the element left the tab order |
|
|
11
|
-
| `prefers-reduced-motion`, `prefers-contrast` | `better-accessibility` | Motion or contrast now ignores the user's system preference |
|
|
12
|
-
| Logical properties swapped for `left` / `right` | `better-layout` | Direction-aware layout was dropped |
|
|
13
|
-
| `lang=`, `dir=` | `better-typography` | Language metadata or text direction was dropped |
|
|
14
|
-
| `text-wrap`, `line-clamp`, `overflow-wrap`, `tabular-nums`, `font-feature-settings` | `better-typography` | Text rendering, wrapping, or numeral alignment silently changed |
|
|
15
|
-
| A color token swapped for a literal, or a token swapped for a lighter one | `better-colors` | The rendered contrast pair may now fail; measure it |
|
|
16
|
-
| A user-facing string deleted or shortened | `better-writing` | A label, error, or empty state lost the information it carried |
|
|
17
|
-
|
|
18
|
-
## Equivalent replacements
|
|
19
|
-
|
|
20
|
-
These clear the signal. Check for them before routing anything, or the report fills with refactors reported as regressions:
|
|
21
|
-
|
|
22
|
-
- `aria-label` giving way to `aria-labelledby` pointing at visible text.
|
|
23
|
-
- An explicit `role` dropped because the element became the native equivalent, `role="button"` going as a `div` becomes a `<button>`.
|
|
24
|
-
- `outline` replaced by a `box-shadow` focus ring that still meets the focus-indicator rule.
|
|
25
|
-
- `tabindex="0"` dropped from an element that is now natively focusable.
|
|
26
|
-
- A color literal replaced by a token that measures the same rendered pair.
|
|
27
|
-
- A physical property replaced by its logical counterpart, which is the fix rather than the regression.
|
|
28
|
-
- A string moved into the translation catalogue rather than deleted.
|
|
29
|
-
|
|
30
|
-
## Searching the removed side
|
|
31
|
-
|
|
32
|
-
Restrict the search to deleted lines so additions do not mask a removal:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
git diff -U0 "$BASE"...HEAD -- '*.tsx' '*.css' | grep -E '^-[^-]' | grep -E 'aria-|role=|alt=|focus|tabindex|prefers-'
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Read the surrounding hunk before deciding. A removed attribute is meaningless without the element it came from, and `-U0` deliberately hides that context.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Review output format
|
|
2
|
-
|
|
3
|
-
This is the format for a review `better-interface` orchestrates. A domain skill reporting on its own carries its own smaller format, in its `## Reporting` section.
|
|
4
|
-
|
|
5
|
-
## Scope and coverage
|
|
6
|
-
|
|
7
|
-
State the exact scope, stack and styling conventions, the project convention documents found in recon and any review boundary. Then show coverage:
|
|
8
|
-
|
|
9
|
-
| Domain | Evidence inspected | Result |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| Accessibility | Files, components, states, or checks | Findings count or `Clear` |
|
|
12
|
-
|
|
13
|
-
Include every domain listed under `better-interface`'s **Use domain skills as the sources of truth**. `Clear` means inspected with no actionable finding; `Not reviewed` must explain why.
|
|
14
|
-
|
|
15
|
-
## Findings
|
|
16
|
-
|
|
17
|
-
One table, ordered by severity, then by reach:
|
|
18
|
-
|
|
19
|
-
| Severity | Domain | Location | Before | After | Why |
|
|
20
|
-
| --- | --- | --- | --- | --- | --- |
|
|
21
|
-
| HIGH | Accessibility | `src/Dialog.tsx:42` | `<button><XIcon /></button>` | Add `aria-label="Close"` and hide the icon from the accessibility tree | The icon-only control has no accessible name |
|
|
22
|
-
|
|
23
|
-
- **Severity** comes from `better-interface`'s **Rank by user impact**.
|
|
24
|
-
- **Location** cites `path/to/file:line`. Cite the exact screen and component when the artifact has no source files.
|
|
25
|
-
- **Before / After** show the current implementation and an actionable replacement. Never split them into separate "Before:" and "After:" lines.
|
|
26
|
-
- **Why** names the violated principle and its user impact.
|
|
27
|
-
- **Domain** is the owning skill without the `better-` prefix.
|
|
28
|
-
|
|
29
|
-
Each row is one root cause. Consolidate a repeated systemic issue into one row and list every affected location. Respect the finding cap. With no findings, omit the table and state "No actionable interface findings."
|
|
30
|
-
|
|
31
|
-
## Verification
|
|
32
|
-
|
|
33
|
-
List each check or interaction, the exact command or steps and the observed result. Separate checks that passed from checks marked **Not verified**.
|
|
34
|
-
|
|
35
|
-
## Verdict
|
|
36
|
-
|
|
37
|
-
End with one of two:
|
|
38
|
-
|
|
39
|
-
- `Block`: one or more `HIGH` findings remain. Do not ship until they are fixed.
|
|
40
|
-
- `Approve`: no `HIGH` findings remain. Any `MEDIUM` and `LOW` findings stay in the table as work to do.
|
|
41
|
-
|
|
42
|
-
`Approve` claims the coverage you reported, so never issue it for a domain you did not inspect.
|
|
43
|
-
|
|
44
|
-
## Change-scoped reviews
|
|
45
|
-
|
|
46
|
-
When `interface-review` resolved the scope from version control, it supplies the scope block, a status on every finding and the change-scoped format, which its `## Review output format` holds. Severity, ranking, the cap and the verdict are the ones above, and all four cover `Introduced` and `Regression` only.
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Scope resolution
|
|
2
|
-
|
|
3
|
-
Turning a review target into a file list. The commands themselves are ordinary git. What follows is the part that is not obvious, plus the traps that fail quietly and leave the scope block claiming a count it never delivered.
|
|
4
|
-
|
|
5
|
-
## Default branch
|
|
6
|
-
|
|
7
|
-
Try `refs/remotes/origin/HEAD`, then `gh repo view --json defaultBranchRef`, then `init.defaultBranch`. If the ref is missing, ask the remote with `git remote set-head origin --auto` rather than guessing. It needs the network and writes a ref under `.git`, leaving the working tree untouched, so it is permitted; note it in Verification. With no remote at all, fall back to a local `main` or `master` and state which base you assumed.
|
|
8
|
-
|
|
9
|
-
## Targets
|
|
10
|
-
|
|
11
|
-
Accepted targets are `working`, `staged`, `branch`, `pr <n>`, a bare `<ref>` and an explicit `<a>..<b>` or `<a>...<b>` range. Anything else in the invocation is a `<ref>`.
|
|
12
|
-
|
|
13
|
-
Diff a branch against the **merge base**, three dots. Two dots reports every upstream commit that landed on the base branch as part of the change.
|
|
14
|
-
|
|
15
|
-
But use the dots the user wrote when they wrote a range. `<a>..<b>` compares the endpoints; `<a>...<b>` compares `merge-base(<a>, <b>)` with `<b>`. Rewriting `release..feature` to three dots drops everything between `release` and the merge base, which is often exactly what was asked for. State the resolved range in the scope block.
|
|
16
|
-
|
|
17
|
-
`git diff HEAD` reports tracked changes only. Any target including uncommitted work must pair it with `git ls-files --others --exclude-standard`. Otherwise a newly added component is silently dropped from a scope the report claims to cover in full. For `branch` with uncommitted work, report the two counts separately.
|
|
18
|
-
|
|
19
|
-
## Pull requests
|
|
20
|
-
|
|
21
|
-
Fetch the head into a remote-tracking ref, `git fetch origin "pull/<n>/head:refs/remotes/pr/<n>"`, and review it in place. This works for forks, which `origin/<branch>` does not.
|
|
22
|
-
|
|
23
|
-
Read files at that ref with `git show refs/remotes/pr/<n>:path/to/file`. Never open the working-tree copy; on a fork PR it is a different file.
|
|
24
|
-
|
|
25
|
-
`gh pr diff <n>` is a fine shortcut for the patch text. It gives no way to read unchanged context or expand to consumers, so fetch the ref as well.
|
|
26
|
-
|
|
27
|
-
**Citations.** `better-interface` requires `path/to/file:line`, and line numbers from a fetched ref need not match the working tree. Cite against the head ref, and declare that ref and its SHA in the scope block so the numbers resolve.
|
|
28
|
-
|
|
29
|
-
**Intent.** The `title` and `body` from `gh pr view` are the stated intent for **Hold the change to its stated intent**. Add the commit subjects when the body is empty.
|
|
30
|
-
|
|
31
|
-
## Awkward repository states
|
|
32
|
-
|
|
33
|
-
Three worth handling. Everything else fails loudly at `merge-base`: no remote, unrelated histories, a repo with no commits, a moved submodule pointer. Say the base is unresolvable and stop. Never review a range you cannot name.
|
|
34
|
-
|
|
35
|
-
**Detached HEAD.** Use the merge base against the default branch and name the SHA, not a branch, in the scope block.
|
|
36
|
-
|
|
37
|
-
**Shallow clone**, the CI default, where `merge-base` returns nothing. Fetch `--deepen=50`, retry, then `--deepen=200`, then report the scope as unresolvable. Deepening writes to `.git` and not to the working tree, so it is permitted; note it in Verification.
|
|
38
|
-
|
|
39
|
-
**Mid-rebase or mid-merge**, the one that does not fail loudly. `git diff` succeeds and returns something that is not the change, so the review looks fine and is wrong. Detect it with `git rev-parse --git-path` against `rebase-merge`, `rebase-apply`, `MERGE_HEAD` and `CHERRY_PICK_HEAD`. Do not test `.git/` paths directly, because they are not directories inside a linked worktree. Stop and say the tree is mid-operation.
|
|
40
|
-
|
|
41
|
-
## Nothing to review
|
|
42
|
-
|
|
43
|
-
The tree is clean and `HEAD` is not ahead of the merge base. Gather the facts before asking, so the offer is accurate rather than a guess: the current branch, whether the tree is clean, the count ahead of the base, the last commit's SHA and subject and any open pull request from `gh pr status`.
|
|
44
|
-
|
|
45
|
-
`gh pr status` succeeds when no pull request is open. It omits `currentBranch`, so an empty result is an answer, not an error. It fails outright without `gh`, without authentication and on a repository with no GitHub remote. Treat any failure as "no pull request found", say so and offer the remaining routes rather than stopping.
|
|
46
|
-
|
|
47
|
-
Report those facts, then offer the three routes in **With no change, ask rather than invent one**. State the last commit's SHA and subject inside the offer. The user recognises "a1b2c3d Merge pull request #482" as not what they wanted and cannot recognise "the last commit".
|
|
48
|
-
|
|
49
|
-
A whole-repository audit is a different review, not this one with a wider net. Hand the repository to `better-interface` directly, without a scope block, statuses, or a pre-existing section.
|
|
50
|
-
|
|
51
|
-
## Renames
|
|
52
|
-
|
|
53
|
-
Rename detection is on by default for `--name-status`, reporting `R100 old/path new/path`. Raise the window with `--find-renames=40% --find-copies-harder` when a file was moved and edited in the same change.
|
|
54
|
-
|
|
55
|
-
Review a rename as a move, not a delete plus an add. Everything that survived the move is unchanged code, and only the genuine edits are in scope.
|
|
56
|
-
|
|
57
|
-
## Excluded paths
|
|
58
|
-
|
|
59
|
-
Exclude these and name what you excluded in the scope block. They are machine-authored and carry no interface rules.
|
|
60
|
-
|
|
61
|
-
| Category | Patterns |
|
|
62
|
-
| --- | --- |
|
|
63
|
-
| Lockfiles | `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`, `Cargo.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock`, `uv.lock` |
|
|
64
|
-
| Snapshots and fixtures | `__snapshots__/`, `*.snap`, `*.approved.*`, `test-results/`, `playwright-report/` |
|
|
65
|
-
| Generated output | `dist/`, `build/`, `out/`, `.next/`, `.turbo/`, `.svelte-kit/`, `coverage/`, `storybook-static/`, `*.min.js`, `*.min.css`, `*.map` |
|
|
66
|
-
| Generated sources | `*.gen.ts`, `*.generated.*`, `*.d.ts` emitted by a build, GraphQL and Prisma client output |
|
|
67
|
-
| Vendored code | `vendor/`, `third_party/`, `node_modules/` |
|
|
68
|
-
| Binaries and media | `*.png`, `*.jpg`, `*.webp`, `*.avif`, `*.woff2`, `*.mp4`, `*.pdf` |
|
|
69
|
-
|
|
70
|
-
Two exceptions stay in scope. A **font file** added or swapped is a `better-typography` change. An **image** added to a component is a `better-ui` and `better-accessibility` change, through its `alt` text and its outline. Review the code that references them, not the bytes.
|
|
71
|
-
|
|
72
|
-
Apply the exclusions as pathspecs so the file count in the scope block is the reviewed count. Two traps under-exclude silently. `*.lock` catches `yarn.lock` and `Cargo.lock` but not `package-lock.json` or `pnpm-lock.yaml`, so cover every suffix in the table. And `**` needs `glob` magic: without it `*` never crosses `/`, so `**/dist/**` excludes `packages/a/dist/` but misses a root-level `dist/`. Run the diff with and without the pathspecs and confirm the count dropped by exactly the files you named.
|
|
73
|
-
|
|
74
|
-
## Expanding to consumers
|
|
75
|
-
|
|
76
|
-
**A diff is not a surface** expands one hop, two for tokens and primitives. Use the project's own resolver where one exists, otherwise import paths.
|
|
77
|
-
|
|
78
|
-
`git grep` searches the working tree by default. Pass the reviewed ref after the pattern, or on a pull request you search a different revision and miss importers the change itself added. Results come back as `<rev>:path/to/file`; read them with `git show`, never the working-tree copy. Pass `-e` when the pattern starts with a dash, such as a `--color-*` token, or git parses it as an option.
|
|
79
|
-
|
|
80
|
-
For a changed token or theme value, search the token name rather than the file, since consumers reference the name and never import it.
|
|
81
|
-
|
|
82
|
-
Order the consumers by a rule you can evaluate, so the cutoff is reproducible instead of a guess:
|
|
83
|
-
|
|
84
|
-
1. **Route and layout entry points first**, whatever the framework treats as a rendered surface: `app/**/page.*`, `app/**/layout.*`, `pages/**`, `routes/**`, `src/views/**`, `*.astro` pages. Everything else only appears inside one.
|
|
85
|
-
2. **Then by importer count**, since a component pulled in by twenty files carries more of the change than one pulled in by two.
|
|
86
|
-
3. **Break ties by proximity**, same package or feature directory first.
|
|
87
|
-
|
|
88
|
-
Review the first five, state how many you did not expand and say plainly if the ordering was arbitrary past a point.
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Screen readers
|
|
2
|
-
|
|
3
|
-
Visually hidden content, live regions, toasts, alt text and SVG.
|
|
4
|
-
|
|
5
|
-
## Visually hidden content
|
|
6
|
-
|
|
7
|
-
The canonical `.sr-only` pattern hides content visually while keeping it in the accessibility tree:
|
|
8
|
-
|
|
9
|
-
```css
|
|
10
|
-
.sr-only {
|
|
11
|
-
position: absolute;
|
|
12
|
-
width: 1px;
|
|
13
|
-
height: 1px;
|
|
14
|
-
padding: 0;
|
|
15
|
-
margin: -1px;
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
clip: rect(0 0 0 0);
|
|
18
|
-
clip-path: inset(50%);
|
|
19
|
-
white-space: nowrap;
|
|
20
|
-
border: 0;
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Use `1px` boxes, not `0`, because some screen readers skip zero-sized elements. `white-space: nowrap` stops words being read as one run-together string. Never `display: none` or `visibility: hidden`, which remove the content from assistive tech entirely.
|
|
25
|
-
|
|
26
|
-
Tailwind ships this as `sr-only`. Skip links add a focus variant that un-hides it, `focus:not-sr-only` or an override on `:focus`.
|
|
27
|
-
|
|
28
|
-
Use it for context sighted users get visually: `<span class="sr-only">Opens in new tab</span>`, table captions, or an icon-only control's label where `aria-label` isn't an option.
|
|
29
|
-
|
|
30
|
-
## Choosing how to announce a change
|
|
31
|
-
|
|
32
|
-
Work down this list and stop at the first match:
|
|
33
|
-
|
|
34
|
-
1. **Focus moves there anyway**, as with an opened modal or the first invalid field. Nothing extra needed; the focus move is the announcement.
|
|
35
|
-
2. **Tied to a specific control**, such as a field error or character count: `aria-describedby` on the control, announced with the field.
|
|
36
|
-
3. **Non-urgent, not tied to a control**, such as a toast, "Saved", a result count, or a loading state: a polite live region, `role="status"`.
|
|
37
|
-
4. **Urgent and not tied to a control**, such as a form-level failure or session expiry: `role="alert"`.
|
|
38
|
-
|
|
39
|
-
## Live regions
|
|
40
|
-
|
|
41
|
-
Live regions announce content that changes without a page load: toasts, validation, search-result counts, loading states.
|
|
42
|
-
|
|
43
|
-
| Mechanism | Politeness | Use for |
|
|
44
|
-
| --- | --- | --- |
|
|
45
|
-
| `role="status"` (= `aria-live="polite"` + `aria-atomic="true"`) | Waits for a pause | Toasts, "Saved", result counts, loading updates |
|
|
46
|
-
| `role="alert"` (= `aria-live="assertive"` + `aria-atomic="true"`) | Interrupts immediately | Errors and urgent problems only |
|
|
47
|
-
|
|
48
|
-
Rules for reliable announcements:
|
|
49
|
-
|
|
50
|
-
- For repeated polite updates, keep a stable empty region in the DOM before changing its text. Inserting a new polite region with its content is announced inconsistently.
|
|
51
|
-
- Dynamically inserted `role="alert"` content is usually announced, but behavior varies. Use it only for urgent errors not tied to a control, and test the target browser and screen-reader combinations.
|
|
52
|
-
- Default to polite. Overusing `assertive` is the most common live-region mistake, because it interrupts whatever the user was reading.
|
|
53
|
-
- Keep messages short and self-contained. `aria-atomic="true"` re-reads the whole region on change.
|
|
54
|
-
- Never move focus to a toast. Announce it and leave focus where the user is working. Give toasts a generous timeout or a dismiss button, and never put the only path to an action inside an auto-dismissing one.
|
|
55
|
-
|
|
56
|
-
```tsx
|
|
57
|
-
// Region rendered from the start, message injected later
|
|
58
|
-
<div role="status" className="sr-only">
|
|
59
|
-
{statusMessage}
|
|
60
|
-
</div>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
For loading states: set `aria-busy="true"` on the updating region, announce "Loading…" politely, then announce the outcome ("Loaded, 12 results").
|
|
64
|
-
|
|
65
|
-
## aria-hidden
|
|
66
|
-
|
|
67
|
-
`aria-hidden="true"` removes an element and its whole subtree from assistive tech. Use it for decorative icons and content duplicated for visual effect. Never put it on or above a focusable element, which creates stops you can Tab to that do not exist for a screen reader. Hiding something interactive means removing it from the tab order too.
|
|
68
|
-
|
|
69
|
-
## Alt text
|
|
70
|
-
|
|
71
|
-
Choose by purpose, not by what the image looks like:
|
|
72
|
-
|
|
73
|
-
| Purpose | Alt | Example |
|
|
74
|
-
| --- | --- | --- |
|
|
75
|
-
| Decorative, or redundant with adjacent text | `alt=""` (empty, but present) | Logo next to the company name in text |
|
|
76
|
-
| Informative | Describe the meaning it adds | `alt="Ticket QR code"` |
|
|
77
|
-
| Functional (image is the link/button) | Describe the action or destination | Search icon → `alt="Search"`, not `alt="magnifying glass"` |
|
|
78
|
-
| Image of text | The exact text (better: use real text) | `alt="50% off everything"` |
|
|
79
|
-
| Complex (chart, diagram) | Short summary in `alt`, full data as a table or text nearby | `alt="Revenue by quarter, described below"` |
|
|
80
|
-
|
|
81
|
-
A missing `alt` is worse than an empty one, because screen readers fall back to reading the file name.
|
|
82
|
-
|
|
83
|
-
## SVG
|
|
84
|
-
|
|
85
|
-
- Decorative SVG: `aria-hidden="true"` and `focusable="false"`, the latter for legacy Edge and IE tabbing. No title needed.
|
|
86
|
-
- Meaningful inline SVG: `role="img"` plus `aria-label="…"`, or a `<title>` as the first child referenced by `aria-labelledby`.
|
|
87
|
-
- Simple cases: `<img src="icon.svg" alt="…">` is the most reliable delivery.
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
// Decorative icon inside a labeled button
|
|
91
|
-
<button aria-label="Close">
|
|
92
|
-
<svg aria-hidden="true" focusable="false">…</svg>
|
|
93
|
-
</button>
|
|
94
|
-
|
|
95
|
-
// Standalone meaningful icon
|
|
96
|
-
<svg role="img" aria-label="Verified account">…</svg>
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Video and audio
|
|
100
|
-
|
|
101
|
-
Prerecorded video needs captions; provide transcripts for audio. Never autoplay with sound, and always render controls.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Semantics and ARIA
|
|
2
|
-
|
|
3
|
-
Native elements first, landmarks, accessible names and the ARIA rules that keep custom widgets honest.
|
|
4
|
-
|
|
5
|
-
## The rules of ARIA
|
|
6
|
-
|
|
7
|
-
1. If a native HTML element with the semantics and behavior you need exists, use it instead of repurposing another element with ARIA.
|
|
8
|
-
2. Don't change native semantics unless you really have to.
|
|
9
|
-
3. Every interactive ARIA control must be keyboard-operable; a role is a promise of the full keyboard model, states and behavior.
|
|
10
|
-
4. Never put `role="presentation"` or `aria-hidden="true"` on a focusable element.
|
|
11
|
-
5. All interactive elements must have an accessible name.
|
|
12
|
-
|
|
13
|
-
No ARIA is better than bad ARIA. A screen reader trusts your roles, so a wrong one is worse than none.
|
|
14
|
-
|
|
15
|
-
## Button vs link vs div
|
|
16
|
-
|
|
17
|
-
| Element | Use for | Why |
|
|
18
|
-
| --- | --- | --- |
|
|
19
|
-
| `<a href>` | Navigation: anything that goes somewhere or changes the URL | Free Cmd/Ctrl/middle-click, right-click → copy link, Enter activation |
|
|
20
|
-
| `<button>` | Actions: submit, toggle, open, delete | Free focus, Enter *and* Space activation, form semantics |
|
|
21
|
-
| `<div onClick>` | Nothing | No role, no focus, no keyboard; screen readers see plain text |
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
// Bad: invisible to keyboard and screen readers
|
|
25
|
-
<div onClick={openSettings}>Settings</div>
|
|
26
|
-
|
|
27
|
-
// Good: focus, Enter/Space activation and semantics for free
|
|
28
|
-
<button onClick={openSettings}>Settings</button>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
If it looks clickable it must be clickable, and if it's clickable it must be a real interactive element. Rebuilding a link as a button, or the reverse, breaks user expectations. A "button" that navigates is a styled `<a>`.
|
|
32
|
-
|
|
33
|
-
Where a native element is truly impossible, the full polyfill is `role="button"` plus `tabindex="0"` plus Enter and Space handlers, which is why the native element is always less code.
|
|
34
|
-
|
|
35
|
-
## Landmarks and headings
|
|
36
|
-
|
|
37
|
-
- Expose one visible primary `<main>` landmark. `<header>`, `<nav>`, `<aside>`, `<footer>` map to landmarks screen-reader users jump between.
|
|
38
|
-
- Multiple landmarks of the same type need distinguishing labels: `<nav aria-label="Primary">`, `<nav aria-label="Breadcrumbs">`.
|
|
39
|
-
- Do not report the one-`<h1>` or the no-skipped-levels convention as a standalone WCAG failure without a concrete navigation or comprehension impact. Headings are structure, not styling; style a heading level with CSS instead of picking the tag by size.
|
|
40
|
-
- `<title>` matches the current context, most specific first: `Billing · Settings · Acme`.
|
|
41
|
-
|
|
42
|
-
## Accessible names
|
|
43
|
-
|
|
44
|
-
Name precedence: `aria-labelledby` > `aria-label` > native label (`<label>`, text content, `alt`) > `title` attribute.
|
|
45
|
-
|
|
46
|
-
- Prefer visible text or `aria-labelledby` over `aria-label`, which is invisible, drifts out of sync with the UI and is handled inconsistently by translation tools.
|
|
47
|
-
- Icon-only buttons always need a name: `<button aria-label="Close">` with the icon `aria-hidden="true"`.
|
|
48
|
-
- WCAG 2.5.3 Label in Name: a button showing "Send" with `aria-label="Submit message"` breaks voice control users who say "click Send".
|
|
49
|
-
- Accessible names must exist even when the design omits visible labels.
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
// Good: name from visible text, icon hidden
|
|
53
|
-
<button>
|
|
54
|
-
<TrashIcon aria-hidden="true" /> Delete
|
|
55
|
-
</button>
|
|
56
|
-
|
|
57
|
-
// Good: icon-only, explicit name
|
|
58
|
-
<button aria-label="Delete">
|
|
59
|
-
<TrashIcon aria-hidden="true" />
|
|
60
|
-
</button>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Add `translate="no"` to brand names, code tokens and identifiers so auto-translation doesn't garble them.
|
|
64
|
-
|
|
65
|
-
## Common ARIA mistakes
|
|
66
|
-
|
|
67
|
-
| Mistake | Why it fails |
|
|
68
|
-
| --- | --- |
|
|
69
|
-
| `aria-label` on a plain `<div>` or `<span>` | Most screen readers ignore names on non-interactive, role-less elements |
|
|
70
|
-
| `<button role="button">` | Redundant role; adds noise, no benefit |
|
|
71
|
-
| `aria-hidden="true"` on or above a focusable element | Creates elements you can Tab to but that don't exist for screen readers |
|
|
72
|
-
| `aria-labelledby`/`aria-describedby` pointing at a missing ID | Silently produces no name or description |
|
|
73
|
-
| `role="menu"` on a nav list | `menu` promises app-style arrow-key behavior; site navigation is `<nav>` with a list |
|
|
74
|
-
|
|
75
|
-
## Disabled states
|
|
76
|
-
|
|
77
|
-
Native `disabled` supplies the platform's complete disabled behavior. It removes the control from the tab order, suppresses activation, applies `:disabled` and excludes form controls from submission. Use it when a native control is genuinely unavailable. `aria-disabled="true"` only announces the state, changing neither focusability, nor behavior, nor styling.
|
|
78
|
-
|
|
79
|
-
- Never disable submit buttons. Keep them enabled, validate on submit and focus the first error ([forms.md](forms.md)).
|
|
80
|
-
- A natively `disabled` control suppresses pointer events and leaves the tab order, so a tooltip on it never opens for keyboard or touch users and is unreliable for mouse users. Put the reason in visible text beside the control, or switch to `aria-disabled="true"`, which keeps it focusable and hoverable and can carry a tooltip.
|
|
81
|
-
- Use `aria-disabled="true"` where keeping a control discoverable in the tab order is intentional, or where a custom control cannot use native `disabled`.
|
|
82
|
-
- With `aria-disabled="true"`, block pointer and keyboard activation in the handler, prevent form submission where applicable, add explicit styling including forced-colors support and explain nearby why the action is unavailable.
|
|
83
|
-
- Never set both `disabled` and `aria-disabled` on the same element.
|
|
84
|
-
- Disabled controls are exempt from contrast minimums. Keep them legible anyway.
|