@ctxr/skill-frontend-excellence 0.1.1
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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/SKILL.md +227 -0
- package/package.json +63 -0
- package/references/accessibility.md +396 -0
- package/references/audit-workflow.md +390 -0
- package/references/components.md +247 -0
- package/references/data-viz.md +457 -0
- package/references/defects.md +152 -0
- package/references/design.md +513 -0
- package/references/forms.md +485 -0
- package/references/lighthouse.md +242 -0
- package/references/motion.md +642 -0
- package/references/performance.md +416 -0
- package/references/pre-launch.md +342 -0
- package/references/responsive.md +519 -0
- package/references/seo.md +422 -0
- package/references/ui-ux.md +565 -0
- package/scripts/check-no-dashes.mjs +90 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# Multi-Page Audit and Polish Workflow
|
|
2
|
+
|
|
3
|
+
The procedural playbook for auditing an existing multi-page site against a reference and polishing it to that level of discipline. Use this when the work is not building a new page from scratch but bringing an existing surface to a consistent visual bar across every route.
|
|
4
|
+
|
|
5
|
+
The strategic four-phase workflow in the entry SKILL.md (Frame, Plan, Build, Verify) is the right frame for greenfield work. The 19 phases below are the right frame for audit and polish work. Run them in order; each phase has a clear input and a clear output.
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
Treat the rendered browser as the source of truth. Do not judge visual quality from code alone. Capture screenshots, inspect them, compare against the reference, patch the smallest set of templates, styles, or assets needed, and re-capture until the visible result is clean at every requested viewport.
|
|
10
|
+
|
|
11
|
+
The goal is not to copy another brand pixel-for-pixel unless explicitly requested. The goal is to match the reference site's level of discipline: proportion, density, hierarchy, alignment, typography, spacing, interaction behavior, responsive refinement, and absence of visual defects.
|
|
12
|
+
|
|
13
|
+
Equally important: repeated widgets must be standardized. If the same kind of thing appears on multiple pages, such as a hero, feature card, pricing card, integration tile, CTA band, testimonial, FAQ, footer column, media row, badge, form, or navigation dropdown, it should follow one canonical visual and content contract unless there is a deliberate, documented variant. Polishing each instance separately is not enough.
|
|
14
|
+
|
|
15
|
+
## Operating Rules
|
|
16
|
+
|
|
17
|
+
These rules apply to every phase below. Treat any violation as a defect.
|
|
18
|
+
|
|
19
|
+
1. Identify the target URL, project root, build system, source directories, and reference URL or screenshots before making changes.
|
|
20
|
+
2. Capture a baseline before editing when visual polish is the task.
|
|
21
|
+
3. Capture at least one desktop viewport and one mobile viewport. Use `1440x900` and `375x812` unless the user specifies otherwise. See [responsive.md](responsive.md) for the canonical breakpoints table.
|
|
22
|
+
4. Audit route-by-route. Include generated pages, legal pages, pricing pages, integration pages, 404 pages, and programmatic content routes if they are linked or published.
|
|
23
|
+
5. Audit component-by-component across routes. Build a widget inventory and compare every repeated widget type across pages before finalizing. See [components.md](components.md).
|
|
24
|
+
6. Extract repeated markup into reusable components, partials, includes, or framework-native primitives when the local stack supports it. If extraction is not feasible, normalize the markup and document why duplication remains.
|
|
25
|
+
7. Compare real screenshots against the reference. Vague statements such as "looks better" are not evidence.
|
|
26
|
+
8. Fix root component causes when the same issue appears across pages: navigation, footer, cards, buttons, typography, spacing tokens, image rules, or grid primitives.
|
|
27
|
+
9. Re-render and re-screenshot after each meaningful group of fixes.
|
|
28
|
+
10. Preserve the site's content, brand intent, and information architecture unless the user asks for copy or structure changes.
|
|
29
|
+
11. Keep changes scoped. Do not rewrite the entire design system when a token, component, template, or layout rule fixes the defect.
|
|
30
|
+
12. Run available validation commands before final delivery: build, lint, stylelint, tests, visual capture, accessibility checks, Lighthouse, and the geometry sweep in [defects.md](defects.md).
|
|
31
|
+
13. Never declare the pass complete while known visible issues remain.
|
|
32
|
+
|
|
33
|
+
## Phase 1: Discover Context
|
|
34
|
+
|
|
35
|
+
Before capturing or editing, determine:
|
|
36
|
+
|
|
37
|
+
- Project root and package manager.
|
|
38
|
+
- Build system and templating mechanism (whatever the project uses; the workflow does not depend on a specific framework).
|
|
39
|
+
- Source locations for templates, components, global CSS, component CSS, JavaScript, image assets, fonts, config, and generated output.
|
|
40
|
+
- Local dev URL and whether a server is already running.
|
|
41
|
+
- Reference source: live URL, screenshot folder, design export, previous production site, or written design standard.
|
|
42
|
+
- Route scope: entire site, root only, a section, or a list of specific routes.
|
|
43
|
+
|
|
44
|
+
Read the project's existing scripts before inventing commands. If a dev server is needed and none is running, start it. If the standard port is occupied, use the existing server when it matches the project; otherwise pick another port.
|
|
45
|
+
|
|
46
|
+
## Phase 2: Inventory Routes
|
|
47
|
+
|
|
48
|
+
Build a route list from multiple sources:
|
|
49
|
+
|
|
50
|
+
- Linked anchors on the homepage and primary navigation.
|
|
51
|
+
- Static-build output directories for routes the user can land on.
|
|
52
|
+
- Sitemap files.
|
|
53
|
+
- Route manifests, content data files, collection templates, or programmatic page data.
|
|
54
|
+
- Known required pages from the user.
|
|
55
|
+
|
|
56
|
+
Exclude only routes that are intentionally external, authenticated admin flows, logout or delete actions, file or mail or tel links, or fragments of pages already covered.
|
|
57
|
+
|
|
58
|
+
If route generation is broken, fix missing published pages before polishing the rest of the site. A polished 404 for a page that should exist is still a failure.
|
|
59
|
+
|
|
60
|
+
## Phase 3: Inventory Repeated Widgets
|
|
61
|
+
|
|
62
|
+
Before editing styles, build a cross-page widget inventory. The inventory is mandatory for any multi-page polish.
|
|
63
|
+
|
|
64
|
+
The 11 widget families and the inventory record format live in [components.md](components.md). For each family, record routes where it appears, the source that renders it, required and optional content fields, allowed variants, the canonical visual contract, and which deviations are intentional versus accidental.
|
|
65
|
+
|
|
66
|
+
Use this inventory to decide whether to extract, extend, or normalize. The extraction rule (extract at three or more duplicates, or at two with clear future reuse, or any visible drift) is the same one in [components.md](components.md). Do not keep two visually different implementations of the same widget type unless they have distinct semantic purposes and named variants.
|
|
67
|
+
|
|
68
|
+
## Phase 4: Capture Baseline
|
|
69
|
+
|
|
70
|
+
Capture every route in scope at both audit viewports before editing. Store screenshots in a timestamped audit directory. Capture both viewport screenshots and full-page screenshots when useful.
|
|
71
|
+
|
|
72
|
+
Use consistent file names so before-and-after comparison is mechanical:
|
|
73
|
+
|
|
74
|
+
- `<route>_desktop.png`
|
|
75
|
+
- `<route>_desktop_full.png`
|
|
76
|
+
- `<route>_mobile.png`
|
|
77
|
+
- `<route>_mobile_full.png`
|
|
78
|
+
- `reference_desktop.png`
|
|
79
|
+
- `reference_mobile.png`
|
|
80
|
+
- `report.json`
|
|
81
|
+
|
|
82
|
+
Run from a headless browser of your choice (Puppeteer, Playwright, or equivalent). The script below uses only standard browser APIs and standard Node modules; adapt the route list and the output directory for the project under audit.
|
|
83
|
+
|
|
84
|
+
Before running it, install the browser-automation library the script imports (for the example below: `npm install --save-dev puppeteer`). Save the script as `capture.cjs` and invoke it with `node capture.cjs <output-dir>` (the output directory defaults to `visual-audit/`). If the project already uses Playwright or another driver, port the loop body; the screenshot file naming convention is the load-bearing part, not the driver.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const fs = require("fs");
|
|
88
|
+
const path = require("path");
|
|
89
|
+
const puppeteer = require("puppeteer");
|
|
90
|
+
|
|
91
|
+
const baseUrl = process.env.TARGET_URL || "http://localhost:3000";
|
|
92
|
+
const outDir = process.argv[2] || "visual-audit";
|
|
93
|
+
const routes = ["/"];
|
|
94
|
+
const viewports = [
|
|
95
|
+
{ name: "desktop", width: 1440, height: 900 },
|
|
96
|
+
{ name: "mobile", width: 375, height: 812 },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const safeName = (route) =>
|
|
100
|
+
(route === "/"
|
|
101
|
+
? "home"
|
|
102
|
+
: route.replace(/^\/|\/$/g, "").replace(/[^a-z0-9]+/gi, "_").toLowerCase()
|
|
103
|
+
) || "home";
|
|
104
|
+
|
|
105
|
+
(async () => {
|
|
106
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
107
|
+
const browser = await puppeteer.launch({ headless: "new" });
|
|
108
|
+
const failures = [];
|
|
109
|
+
for (const route of routes) {
|
|
110
|
+
for (const vp of viewports) {
|
|
111
|
+
const page = await browser.newPage();
|
|
112
|
+
await page.setViewport({ width: vp.width, height: vp.height, deviceScaleFactor: 1 });
|
|
113
|
+
try {
|
|
114
|
+
const response = await page.goto(`${baseUrl}${route}?qa=${Date.now()}`, {
|
|
115
|
+
waitUntil: "networkidle0", timeout: 30000,
|
|
116
|
+
});
|
|
117
|
+
if (response && response.status() >= 400) {
|
|
118
|
+
failures.push({ route, viewport: vp.name, status: response.status() });
|
|
119
|
+
}
|
|
120
|
+
const file = path.join(outDir, `${safeName(route)}_${vp.name}.png`);
|
|
121
|
+
await page.screenshot({ path: file });
|
|
122
|
+
await page.screenshot({ path: file.replace(".png", "_full.png"), fullPage: true });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
failures.push({ route, viewport: vp.name, error: String(error.message || error) });
|
|
125
|
+
} finally {
|
|
126
|
+
await page.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await browser.close();
|
|
131
|
+
fs.writeFileSync(path.join(outDir, "report.json"), JSON.stringify({ baseUrl, routes, failures }, null, 2));
|
|
132
|
+
})();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Adapt the script rather than rewriting it. Add route discovery, contact sheets, interactive captures, and the geometry sweep from [defects.md](defects.md) as the task requires.
|
|
136
|
+
|
|
137
|
+
## Phase 5: Capture Reference
|
|
138
|
+
|
|
139
|
+
Capture the reference target at the same viewport sizes used in Phase 4. If the reference is a live site, use cache-busted URLs and the same browser engine as the baseline. If the reference is a screenshot or design export, place the assets next to the local screenshots so side-by-side comparison is one open command away.
|
|
140
|
+
|
|
141
|
+
When comparing to a reference:
|
|
142
|
+
|
|
143
|
+
- Match polish level, not necessarily exact color or content.
|
|
144
|
+
- Compare the first viewport first: navigation, hero, primary CTA, image treatment, first hint of next section.
|
|
145
|
+
- Then compare section rhythm: density, vertical spacing, card grids, media-to-copy balance, and CTA placement.
|
|
146
|
+
- Finally compare details: shadows, borders, radii, icon alignment, label treatment, focus states, hover states, mobile drawer behavior, footer density.
|
|
147
|
+
|
|
148
|
+
## Phase 6: Audit Each Route
|
|
149
|
+
|
|
150
|
+
For every route and viewport, inspect every category below. Record findings as route plus viewport plus component plus what is wrong plus why it matters plus likely source plus screenshot plus fix status.
|
|
151
|
+
|
|
152
|
+
| Category | What to inspect |
|
|
153
|
+
|----------|-----------------|
|
|
154
|
+
| Header and navigation | Logo alignment, nav item spacing, dropdown centering, active state, hover/focus/pressed, sticky behavior, z-index, seam lines, mobile drawer, scroll lock |
|
|
155
|
+
| Hero | First-viewport composition, heading scale, line length, CTA alignment, media size, image sharpness, next-section reveal, mobile fold |
|
|
156
|
+
| Sections | Vertical rhythm, background bands, max-widths, gutters, content density, section transitions |
|
|
157
|
+
| Cards and tiles | Equal heights in rows, equal widths, consistent padding, aligned baselines, clickable area, hover, focus ring, icon and image alignment, aspect ratio |
|
|
158
|
+
| Buttons and links | Size, label clarity, icon centering, hover drift, duplicate arrows, disabled states, touch target, focus visibility |
|
|
159
|
+
| Typography | Family, weight, scale, line-height, letter-spacing, wrapping, measure, hierarchy, alignment |
|
|
160
|
+
| Color | Background, foreground, accent consistency, contrast, border, hover, active, muted text, light and dark bands |
|
|
161
|
+
| Images and media | Crop, aspect ratio, resolution, `srcset`, lazy and eager loading, alt text, visual relevance, repeated or placeholder assets |
|
|
162
|
+
| Forms | Labels, placeholders, validation states, focus, disabled, field heights, mobile keyboard fit |
|
|
163
|
+
| Footer | Layout balance, link columns, legal row, social icon spacing, hit area, mobile stacking |
|
|
164
|
+
| Responsive behavior | No horizontal scroll, no clipping, no desktop layout on mobile, no text overflow, no oversized buttons or cards, no tiny tap targets |
|
|
165
|
+
|
|
166
|
+
Also compare each widget against other instances of the same family. Do same-type cards have the same padding, radius, border, hover, min-height, CTA position, and typography? Does the same CTA mean the same thing and use the same label style? Do integration and logo tiles use one image size, one logo treatment, and one fallback? Do section headers use the same eyebrow, H2, subhead, width, and gap rules? Do hero variants have named purposes and shared internals? Are component variants explicit in code, or are they accidental class combinations?
|
|
167
|
+
|
|
168
|
+
## Phase 7: Prioritize Fixes
|
|
169
|
+
|
|
170
|
+
Fix in this order. Higher tiers always block lower tiers.
|
|
171
|
+
|
|
172
|
+
1. Broken routes, 404s, missing generated pages, hard layout failures.
|
|
173
|
+
2. Global shell defects: header, navigation, footer, body overflow, typography tokens, color tokens.
|
|
174
|
+
3. Cross-page widget drift: same widget type implemented differently across pages without a named variant.
|
|
175
|
+
4. Cross-page component defects: cards, buttons, pricing, integration tiles, form controls, media blocks.
|
|
176
|
+
5. Page-specific hierarchy and section density issues; interaction and accessibility issues; asset and performance polish; minor copy clarity where misleading product claims or repeated template text needs work.
|
|
177
|
+
|
|
178
|
+
Prefer global fixes when a defect repeats. Prefer page-level fixes when the defect is truly contextual.
|
|
179
|
+
|
|
180
|
+
## Phase 8: Patch With Component Discipline
|
|
181
|
+
|
|
182
|
+
When repeated widgets drift, fix the component system before tuning page-specific CSS. The full extraction sequence and the component contract checklist live in [components.md](components.md). Walk that file before editing.
|
|
183
|
+
|
|
184
|
+
The summary: choose a canonical contract, name the widget and variants clearly, move repeated markup into the project's component mechanism, pass content as explicit fields, make variants explicit, centralize CSS, re-render every route, and compare instances side-by-side at both viewports. Anti-patterns to avoid (copy-paste tweaking, near-duplicate components, one-off CSS for drift, overly generic components requiring per-call-site overrides) also live in [components.md](components.md).
|
|
185
|
+
|
|
186
|
+
## Phase 9: Patch With Design Discipline
|
|
187
|
+
|
|
188
|
+
Use existing design primitives before adding new ones. Patch CSS, templates, and components conservatively.
|
|
189
|
+
|
|
190
|
+
- Normalize tokens first: spacing, color, border, radius, shadow, type scale. Token detail lives in [design.md](design.md).
|
|
191
|
+
- Use stable dimensions for repeated UI: `min-height`, `aspect-ratio`, grid tracks, `align-items: stretch`, fixed icon boxes.
|
|
192
|
+
- Avoid arbitrary one-off margins. Use local precedent or none.
|
|
193
|
+
- Use `gap` for internal spacing instead of stacked margins where possible.
|
|
194
|
+
- Use `max-width` and responsive constraints for readable line lengths. Layout primitive detail lives in [responsive.md](responsive.md).
|
|
195
|
+
- Use full-card anchors when a card visually behaves as a link. Do not nest anchors.
|
|
196
|
+
- Keep CTA hover transforms from stacking with card hover transforms.
|
|
197
|
+
- Use `:focus-visible` rings that are clearly visible and consistent. Contrast detail lives in [accessibility.md](accessibility.md).
|
|
198
|
+
- Avoid hiding visible text behind mismatched `aria-label`. Visible text is the accessible name.
|
|
199
|
+
- Make hover and focus states visually related so a keyboard user gets the same clarity as a pointer user.
|
|
200
|
+
- Use `srcset` and `sizes` for recurring large images.
|
|
201
|
+
- Use `loading="eager"` and `fetchpriority="high"` only on the hero LCP image. Use `loading="lazy"` and `decoding="async"` for below-the-fold imagery. Image strategy detail lives in [performance.md](performance.md).
|
|
202
|
+
|
|
203
|
+
## Phase 10: Verify After Every Fix Group
|
|
204
|
+
|
|
205
|
+
After a meaningful fix group:
|
|
206
|
+
|
|
207
|
+
1. Rebuild or let the dev server hot reload.
|
|
208
|
+
2. Re-capture the affected routes at desktop and mobile.
|
|
209
|
+
3. Compare before and after.
|
|
210
|
+
4. Inspect at least the first viewport and the edited section.
|
|
211
|
+
5. Confirm no regression on shared components.
|
|
212
|
+
6. If a reusable widget changed, capture every route where the widget appears, not only the route where the defect was first noticed.
|
|
213
|
+
|
|
214
|
+
Do not wait until the end to discover that a global CSS fix broke mobile.
|
|
215
|
+
|
|
216
|
+
## Phase 11: Programmatic Geometry Checks
|
|
217
|
+
|
|
218
|
+
Use browser automation to catch issues that screenshots miss: viewport bleed, hidden text overflow, sub-44 hit targets, duplicate arrows, dropdown miscentering, mobile drawer scroll-lock failures, focus invisibility.
|
|
219
|
+
|
|
220
|
+
The canonical 9-check sweep, the JS snippet that runs it, and the per-check thresholds live in [defects.md](defects.md). Run it on every audited route at both capture viewports. A run is clean when every check returns zero issues.
|
|
221
|
+
|
|
222
|
+
Filter false positives only when you can explain them, such as hidden off-canvas content or intentionally overflowing dropdown internals that do not affect the page viewport. Document the filter so the next run does not re-discover it.
|
|
223
|
+
|
|
224
|
+
## Phase 12: Component Drift Checks
|
|
225
|
+
|
|
226
|
+
Use browser automation to compare same-family widgets across pages. This does not replace visual judgment, but it catches drift early.
|
|
227
|
+
|
|
228
|
+
The drift collector snippet and the property list (width, height, padding, radius, border, background, shadow, typography, CTA position, image metrics) live in [components.md](components.md). Run it on every route in scope. Compare the result for each family across pages.
|
|
229
|
+
|
|
230
|
+
Flag any difference that is not explained by a named variant. A family is drift-free when every same-variant instance produces the same metrics within rounding tolerance.
|
|
231
|
+
|
|
232
|
+
## Phase 13: Interaction QA
|
|
233
|
+
|
|
234
|
+
Capture explicit interaction screenshots and verify behavior:
|
|
235
|
+
|
|
236
|
+
- Desktop dropdown open.
|
|
237
|
+
- Mobile menu open.
|
|
238
|
+
- Hovered card or button.
|
|
239
|
+
- Focus-visible state on major controls.
|
|
240
|
+
- Pricing card hover or focus if cards animate.
|
|
241
|
+
- Modal, drawer, accordion, tabs, carousel, or form validation states if present.
|
|
242
|
+
|
|
243
|
+
Behavioral checks:
|
|
244
|
+
|
|
245
|
+
- Dropdowns are centered or intentionally aligned.
|
|
246
|
+
- Dropdowns stay within the viewport.
|
|
247
|
+
- Esc closes popovers.
|
|
248
|
+
- Outside click closes popovers.
|
|
249
|
+
- Mobile drawer locks page scroll while open and restores scroll after close.
|
|
250
|
+
- Buttons do not drift on hover.
|
|
251
|
+
- Active and current nav states are visible.
|
|
252
|
+
- Focus ring is not clipped.
|
|
253
|
+
- Interactive element accessible names match visible labels.
|
|
254
|
+
|
|
255
|
+
State coverage detail (empty, loading, success, error, partial, disabled, read-only, stale, offline, unauthorized, limit-reached, initial, done) lives in [ui-ux.md](ui-ux.md).
|
|
256
|
+
|
|
257
|
+
## Phase 14: Accessibility Validation
|
|
258
|
+
|
|
259
|
+
Use Lighthouse, axe, or equivalent when available. Treat these as baseline checks, not replacements for human visual inspection. The full WCAG 2.2 AA framework, ARIA rules, contrast targets, keyboard patterns, screen reader checks, and focus management live in [accessibility.md](accessibility.md).
|
|
260
|
+
|
|
261
|
+
The minimum required basics specific to multi-page polish:
|
|
262
|
+
|
|
263
|
+
- `<html lang>` exists and is valid on every route.
|
|
264
|
+
- Exactly one primary `<main>` per route.
|
|
265
|
+
- Logical heading order; one H1 per route; sequential H2 to H3.
|
|
266
|
+
- Links and buttons have accessible names.
|
|
267
|
+
- Form controls have labels.
|
|
268
|
+
- Visible labels match accessible names.
|
|
269
|
+
- Images have useful alt text or empty decorative alt.
|
|
270
|
+
- Contrast passes for text, UI controls, and focus indicators in both light and dark mode.
|
|
271
|
+
- Focus order follows visual order.
|
|
272
|
+
- No keyboard traps.
|
|
273
|
+
- Skip link works and does not create viewport overflow while hidden.
|
|
274
|
+
|
|
275
|
+
If a visual fix creates an accessibility problem, the visual fix is incomplete.
|
|
276
|
+
|
|
277
|
+
## Phase 15: Lighthouse and Performance Polish
|
|
278
|
+
|
|
279
|
+
Run Lighthouse when the project has it set up or when the user asks for production polish. Fix failing assertions. Treat warnings as useful triage but do not over-optimize at the cost of design unless the user asks. Lighthouse setup, scoring weights, and audit-by-audit fixes live in [lighthouse.md](lighthouse.md). Performance mechanics and asset strategy live in [performance.md](performance.md).
|
|
280
|
+
|
|
281
|
+
Common visual-performance fixes that surface in audit work:
|
|
282
|
+
|
|
283
|
+
- Add responsive `srcset` variants for hero and recurring large images.
|
|
284
|
+
- Use correct intrinsic `width` and `height` on every image to prevent layout shift.
|
|
285
|
+
- Preload or prioritize only the actual LCP image.
|
|
286
|
+
- Avoid loading offscreen imagery eagerly.
|
|
287
|
+
- Remove giant decorative images that are visually indistinguishable from smaller assets.
|
|
288
|
+
- Keep webfont usage intentional. Cap to two families and four weights.
|
|
289
|
+
- Avoid expensive shadows and filters on large scrolling surfaces if they cause jank.
|
|
290
|
+
|
|
291
|
+
## Phase 16: Reference-Level Design Heuristics
|
|
292
|
+
|
|
293
|
+
Use these tests when comparing the audited site to the reference. Each test is a yes-or-no judgment supported by the captured screenshots. The same list lives in [design.md](design.md) "Reference Comparison Heuristics" for use outside this audit workflow; the two copies are intentionally identical so each file is self-contained for its reader's mode.
|
|
294
|
+
|
|
295
|
+
1. First viewport has a clear hierarchy and does not feel accidental.
|
|
296
|
+
2. The brand or product is visible immediately when relevant.
|
|
297
|
+
3. The next section peeks intentionally rather than crowding the hero.
|
|
298
|
+
4. Repeated cards in a row align on top edges, bottom edges, and CTA baselines.
|
|
299
|
+
5. Text blocks have readable line lengths and consistent rhythm.
|
|
300
|
+
6. Labels, eyebrows, and badges use one visual language.
|
|
301
|
+
7. Buttons use one size system and one radius system.
|
|
302
|
+
8. Icons sit optically centered, not just mathematically centered.
|
|
303
|
+
9. Borders are subtle and consistent.
|
|
304
|
+
10. Shadows support hierarchy but do not muddy the palette.
|
|
305
|
+
11. Alternate background bands feel deliberate.
|
|
306
|
+
12. Mobile layout feels designed, not merely stacked.
|
|
307
|
+
13. Footer spacing is calmer than the body, with no stray gaps or cramped links.
|
|
308
|
+
14. Empty states, legal pages, and 404 pages share the same polish as marketing pages.
|
|
309
|
+
15. Same-family widgets look like members of one system across pages.
|
|
310
|
+
16. Variants are recognizably intentional, not accidental drift.
|
|
311
|
+
|
|
312
|
+
A site at reference level passes every test. A site at "fine" level fails three or more.
|
|
313
|
+
|
|
314
|
+
## Phase 17: Common Defects and Fixes
|
|
315
|
+
|
|
316
|
+
The 29-row symptom-to-fix lookup table lives in [defects.md](defects.md). Use it whenever a defect surfaces during Phase 6 or Phase 13. Apply the standard fix at the right layer (component, page, or design token), then re-run the geometry sweep on the affected route.
|
|
317
|
+
|
|
318
|
+
## Phase 18: Deliverables
|
|
319
|
+
|
|
320
|
+
Provide a concise final package:
|
|
321
|
+
|
|
322
|
+
- Summary of main changes.
|
|
323
|
+
- Per-page checklist with issues found, fixes applied, and screenshot references.
|
|
324
|
+
- Before-and-after screenshot paths for desktop and mobile.
|
|
325
|
+
- Reference screenshot paths.
|
|
326
|
+
- Interactive screenshot paths if menus, drawers, or cards were tested.
|
|
327
|
+
- Validation commands and pass-or-fail results.
|
|
328
|
+
- Remaining risks only if real issues remain.
|
|
329
|
+
- Widget inventory and standardization notes: which repeated widgets were found, which were extracted or normalized, and which named variants remain.
|
|
330
|
+
|
|
331
|
+
Per-page checklist format:
|
|
332
|
+
|
|
333
|
+
```markdown
|
|
334
|
+
| Route | Issues found | Fixes applied | Before | After |
|
|
335
|
+
| --- | --- | --- | --- | --- |
|
|
336
|
+
| `/pricing/` | Pricing CTA drift, partial card click area | Full-card anchors, stable hover state | [D](before/pricing_desktop.png) [M](before/pricing_mobile.png) | [D](after/pricing_desktop.png) [M](after/pricing_mobile.png) |
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Widget inventory format:
|
|
340
|
+
|
|
341
|
+
```markdown
|
|
342
|
+
| Widget family | Routes | Source | Standardization action | Variants |
|
|
343
|
+
| --- | --- | --- | --- | --- |
|
|
344
|
+
| Integration tile | `/integrations/`, `/whatsapp/`, `/for/.../` | duplicated markup plus partial | Extracted to `integration-card` | `link`, `static-logo` |
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Validation summary format:
|
|
348
|
+
|
|
349
|
+
```markdown
|
|
350
|
+
- Build: passed
|
|
351
|
+
- Lint: passed
|
|
352
|
+
- Capture: 24 routes, `failures: []`
|
|
353
|
+
- Geometry sweep: `[]` at `1440x900` and `375x812`
|
|
354
|
+
- Drift collector: zero unexplained differences across families
|
|
355
|
+
- Lighthouse: passed required assertions
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Phase 19: Final Acceptance Gate
|
|
359
|
+
|
|
360
|
+
Before final delivery, confirm every item below. This is the authoritative gate for multi-page audit and polish work. The Self-Improvement section in the entry SKILL.md is the short version.
|
|
361
|
+
|
|
362
|
+
- Every in-scope route was captured at desktop (`1440x900`) and mobile (`375x812`).
|
|
363
|
+
- Every route returns the expected HTTP status.
|
|
364
|
+
- Repeated widget families have been inventoried and compared across pages.
|
|
365
|
+
- Same-purpose widgets have been extracted into reusable components or partials, or normalized to one contract where extraction is not feasible.
|
|
366
|
+
- Remaining variants are named, intentional, and visually consistent within their variant.
|
|
367
|
+
- Every edited shared component was re-captured on every route where it appears (not just the route where the defect was noticed) and the geometry sweep passed.
|
|
368
|
+
- The latest screenshots reflect the latest code.
|
|
369
|
+
- Build and lint pass, or failures are clearly unrelated and reported.
|
|
370
|
+
- No horizontal scroll exists on mobile.
|
|
371
|
+
- No known text overflow, clipped controls, duplicate arrows, tiny touch targets, or partial clickable-card defects remain.
|
|
372
|
+
- The geometry sweep returns zero issues on every audited route at both capture viewports.
|
|
373
|
+
- The drift collector returns no unexplained differences for any widget family.
|
|
374
|
+
- Navigation, footer, pricing, cards, forms, and interactive states pass Phase 13 interaction QA and Phase 14 accessibility validation.
|
|
375
|
+
- Accessibility basics from Phase 14 are clean on every route.
|
|
376
|
+
- Reference-Level Design Heuristics from Phase 16 pass.
|
|
377
|
+
- The final checklist is written and linked.
|
|
378
|
+
|
|
379
|
+
If any item fails, continue fixing or clearly state the blocker. Do not present an incomplete visual pass as complete.
|
|
380
|
+
|
|
381
|
+
## See Also
|
|
382
|
+
|
|
383
|
+
- [components.md](components.md) for extraction discipline, contracts, and drift detection
|
|
384
|
+
- [defects.md](defects.md) for the symptom-to-fix lookup table and the canonical geometry sweep
|
|
385
|
+
- [accessibility.md](accessibility.md) for the WCAG framework, ARIA, contrast, focus, screen reader
|
|
386
|
+
- [responsive.md](responsive.md) for capture viewports, breakpoints, and layout primitives
|
|
387
|
+
- [design.md](design.md) for tokens, typography, color, and shadow language
|
|
388
|
+
- [performance.md](performance.md) for image, font, and asset strategy
|
|
389
|
+
- [lighthouse.md](lighthouse.md) for audit setup and score-driven fixes
|
|
390
|
+
- [pre-launch.md](pre-launch.md) for the launch gate that runs alongside this workflow
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Component Discipline
|
|
2
|
+
|
|
3
|
+
Framework-agnostic guidance on standardizing repeated widgets across pages, defining component contracts, and detecting drift before it ships. Polishing each instance separately is not enough: same-purpose widgets must follow one canonical visual and content contract.
|
|
4
|
+
|
|
5
|
+
## Terms
|
|
6
|
+
|
|
7
|
+
The rest of this file (and [audit-workflow.md](audit-workflow.md) and [defects.md](defects.md)) uses these terms with the meanings below. Pin them down before reading further.
|
|
8
|
+
|
|
9
|
+
- **Widget family**: a group of repeated UI elements that serve the same role across pages, e.g. all feature cards, all CTA bands, all hero variants. See the 11-family table below.
|
|
10
|
+
- **Canonical contract**: the single, authoritative visual and content specification for a widget family. Names the required and optional content fields, the allowed variants, the CSS properties that are part of the family (padding, radius, shadow, etc.), and the states (hover, focus, active, disabled, loading). A component is not done until the contract is documented and every instance matches it.
|
|
11
|
+
- **Named variant**: an intentional, documented variation of a component with a clear purpose, exposed through one prop, one class, or one data attribute (e.g., `card--feature`, `card--integration`, `card--pricing`). Variants are part of the contract; ad hoc class combinations are not variants, they are drift.
|
|
12
|
+
- **Drift**: a repeated widget that looks or behaves differently across pages without a named variant. Drift indicates a missing component abstraction or an incomplete variant set. Detected by visual comparison and by the drift collector (below).
|
|
13
|
+
- **Drift collector**: a browser-automation script that reads computed styles and bounding boxes for every member of a family across every audited route, then flags any difference not explained by a named variant.
|
|
14
|
+
|
|
15
|
+
## When to Extract
|
|
16
|
+
|
|
17
|
+
Extraction turns repeated markup into a single source of truth. Extract when any of these hold:
|
|
18
|
+
|
|
19
|
+
- The same structure appears three or more times across routes.
|
|
20
|
+
- The same structure appears twice with clear future reuse.
|
|
21
|
+
- Duplicated markup is causing visible drift (different padding, radius, typography, or hover behavior on instances that should look identical).
|
|
22
|
+
|
|
23
|
+
Do not extract when:
|
|
24
|
+
|
|
25
|
+
- The local stack has no include or component mechanism and adding one would add build complexity out of proportion to the benefit. Normalize markup and class names instead.
|
|
26
|
+
- The two instances look similar but serve genuinely different semantic purposes. Build them as named variants rather than forcing one component to flex.
|
|
27
|
+
|
|
28
|
+
The principle expressed as a check: a repeated widget either has one source of truth or has a documented, named variant. There is no third option.
|
|
29
|
+
|
|
30
|
+
## The 11 Widget Families
|
|
31
|
+
|
|
32
|
+
Every multi-page polish pass starts by inventorying these families. For each family the inventory must record: routes where it appears, source (template or component or duplicated markup), required and optional content fields, allowed variants, and the canonical visual contract (spacing, typography, border, radius, shadow, color, icon placement, image ratio, CTA behavior, focus ring, hover behavior, responsive behavior).
|
|
33
|
+
|
|
34
|
+
| Family | Members | Standardization criteria |
|
|
35
|
+
|--------|---------|--------------------------|
|
|
36
|
+
| Global shell | Header, primary nav, dropdowns, mobile drawer, footer, legal/social row | One header, one footer, one drawer; consistent across every route |
|
|
37
|
+
| Hero systems | Centered, asymmetric, product, landing, legal/simple | Each hero variant is a named component; no ad-hoc combinations |
|
|
38
|
+
| Section headers | Eyebrow/label, heading, subheading | One spacing rule, one max-width rule, one alignment rule per variant |
|
|
39
|
+
| CTA systems | Primary button, secondary button, text link, final CTA band, pricing CTA, card CTA | One size scale, one radius scale, one focus ring, one hover transform |
|
|
40
|
+
| Card systems | Feature, benefit, outcome, pain, directory, integration, pricing, FAQ, legal-content | Equal heights in rows, consistent padding, consistent CTA placement |
|
|
41
|
+
| Media systems | Image rows, logo tiles, screenshots, illustrations, icon boxes, avatars | Fixed image box dimensions per family, one alt-text policy, one loading strategy |
|
|
42
|
+
| Data/list systems | Pricing tables, comparison tables, FAQ lists, pain lists, checklists, icon lists | Tabular figures for numbers, one bullet/icon style per list type |
|
|
43
|
+
| Interactive systems | Accordions, tabs, menus, dropdowns, drawers, modals, hover cards, forms | One open/close behavior, one focus management policy, one Esc/outside-click rule |
|
|
44
|
+
| Empty states | Empty list, empty search, zero data | Same polish as marketing surfaces; specific message and primary action |
|
|
45
|
+
| Legal pages | Terms, privacy, acceptable use, security | Shared layout shell; consistent typography and spacing |
|
|
46
|
+
| 404 / error pages | 404, 5xx, offline | Branded, useful (search box, home link), not just an apology |
|
|
47
|
+
|
|
48
|
+
The inventory is mandatory for any multi-page polish. Without it, drift is invisible and standardization is guesswork. See [audit-workflow.md](audit-workflow.md) Phase 3 for the procedure.
|
|
49
|
+
|
|
50
|
+
## Component Contract Checklist
|
|
51
|
+
|
|
52
|
+
A widget is ready for extraction when every item below is true. Before declaring a component done, walk this list.
|
|
53
|
+
|
|
54
|
+
- [ ] Required content fields are documented by usage: title, body, eyebrow, image, CTA, secondary CTA, metadata, icon, badge.
|
|
55
|
+
- [ ] Optional fields have graceful empty states and do not leave blank gaps.
|
|
56
|
+
- [ ] Markup is semantic and stable. Headings use the right level; landmarks are correct; lists are real lists.
|
|
57
|
+
- [ ] Layout is resilient to long text, very short text, and missing optional fields. Test the longest realistic title and the shortest.
|
|
58
|
+
- [ ] Desktop and mobile behavior is defined explicitly, not by accident.
|
|
59
|
+
- [ ] Hover, focus, active, disabled, and loading states are defined when relevant.
|
|
60
|
+
- [ ] Accessible names come from visible content unless a different name is necessary; visible text and `aria-label` agree.
|
|
61
|
+
- [ ] Images have consistent intrinsic dimensions, aspect ratio, loading strategy, and alt behavior.
|
|
62
|
+
- [ ] No page-specific magic numbers exist inside the component unless exposed as a named variant.
|
|
63
|
+
- [ ] The component owns its CSS. Page-local overrides on shared widgets are forbidden.
|
|
64
|
+
- [ ] Variants are explicit (one prop, one class, one data attribute), not arbitrary class combinations.
|
|
65
|
+
|
|
66
|
+
A component shipped without these answers is half-built and will drift on the next page.
|
|
67
|
+
|
|
68
|
+
## Extraction Sequence
|
|
69
|
+
|
|
70
|
+
Follow this sequence. Skipping a step turns extraction into yet another duplicated markup variant.
|
|
71
|
+
|
|
72
|
+
1. Choose the canonical contract. Pick the best existing implementation, the reference target, and the product needs as inputs. The contract is one design, not a union of all current usages.
|
|
73
|
+
2. Name the widget and its variants clearly. Names like `hero-asymmetric`, `section-heading`, `feature-card`, `integration-card`, `pricing-card`, `final-cta`, `alternating-row`, or `faq-list` make intent obvious. Avoid `card`, `section`, `block` (too generic).
|
|
74
|
+
3. Move the repeated markup into the framework's native component or partial mechanism. Applies in any templating system that supports component reuse: server-side partials, single-file components, JSX components, or build-time includes. Pick the mechanism the project already uses.
|
|
75
|
+
4. Pass content as explicit fields. Embedded page-specific text inside shared markup is the most common cause of "we have a component but it still drifts".
|
|
76
|
+
5. Make variants explicit through one prop, one class, or one data attribute. If a variant needs three coordinated changes, encode them in one named variant, not three knobs.
|
|
77
|
+
6. Centralize the CSS for the family and remove or reduce page-local overrides. The component owns its visual contract; page CSS adjusts only what the component exposes.
|
|
78
|
+
7. Re-render every route that uses the widget. A single broken instance proves the component is incomplete.
|
|
79
|
+
8. Compare every instance side-by-side at both audit viewports. If two instances do not look like members of the same system, the contract is wrong or the variant set is incomplete.
|
|
80
|
+
|
|
81
|
+
## What To Avoid
|
|
82
|
+
|
|
83
|
+
These four anti-patterns produce most cross-page drift.
|
|
84
|
+
|
|
85
|
+
- Copy-pasting a polished widget and tweaking classes page by page. The first divergence is the start of the drift; every subsequent page makes the contract harder to recover.
|
|
86
|
+
- Creating near-duplicate components with different names but the same semantic purpose. `feature-card`, `feature-tile`, and `benefit-card` that differ only in padding and CTA copy are one component with one variant axis.
|
|
87
|
+
- Fixing drift by adding more one-off CSS selectors. Page-local overrides reduce the symptom and reproduce the disease at the next site of drift.
|
|
88
|
+
- Making one component so generic that every call site needs overrides to look right. Generic components without strong defaults push the styling burden back to the page, which is where the drift lives.
|
|
89
|
+
|
|
90
|
+
## Drift Detection
|
|
91
|
+
|
|
92
|
+
Programmatic drift detection complements visual review. Collect computed styles and bounding boxes for every member of a family across every route, then flag any difference not explained by a named variant.
|
|
93
|
+
|
|
94
|
+
For each repeated widget selector, collect:
|
|
95
|
+
|
|
96
|
+
- Width, height, padding, border-radius, border color, border width, border style, background, box-shadow.
|
|
97
|
+
- Heading font-size, weight, line-height.
|
|
98
|
+
- Body font-size, line-height, color.
|
|
99
|
+
- CTA position and size.
|
|
100
|
+
- Image and logo rendered size and aspect ratio.
|
|
101
|
+
- Grid row height variance across the row.
|
|
102
|
+
|
|
103
|
+
Run from a headless browser of your choice (Puppeteer, Playwright, or equivalent). The snippet below uses only standard DOM and CSSOM APIs:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const componentMetrics = await page.evaluate(() => {
|
|
107
|
+
const read = (el) => {
|
|
108
|
+
const cs = getComputedStyle(el);
|
|
109
|
+
const r = el.getBoundingClientRect();
|
|
110
|
+
const heading = el.querySelector("h2, h3, .card-title");
|
|
111
|
+
const body = el.querySelector("p");
|
|
112
|
+
const cta = el.querySelector("a, button, .btn, .btn-text");
|
|
113
|
+
const img = el.querySelector("img");
|
|
114
|
+
return {
|
|
115
|
+
selector: el.className,
|
|
116
|
+
width: Math.round(r.width),
|
|
117
|
+
height: Math.round(r.height),
|
|
118
|
+
padding: cs.padding,
|
|
119
|
+
radius: cs.borderRadius,
|
|
120
|
+
background: cs.backgroundColor,
|
|
121
|
+
borderColor: cs.borderColor,
|
|
122
|
+
borderWidth: cs.borderWidth,
|
|
123
|
+
borderStyle: cs.borderStyle,
|
|
124
|
+
shadow: cs.boxShadow,
|
|
125
|
+
headingSize: heading ? getComputedStyle(heading).fontSize : null,
|
|
126
|
+
headingWeight: heading ? getComputedStyle(heading).fontWeight : null,
|
|
127
|
+
bodySize: body ? getComputedStyle(body).fontSize : null,
|
|
128
|
+
bodyLine: body ? getComputedStyle(body).lineHeight : null,
|
|
129
|
+
cta: cta ? cta.getBoundingClientRect().toJSON() : null,
|
|
130
|
+
image: img ? img.getBoundingClientRect().toJSON() : null,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const families = {
|
|
134
|
+
cards: [".feature-card", ".card-link", ".grid-item"],
|
|
135
|
+
integrations: [".integration-card", ".integration-tile", ".integration-logo-tile"],
|
|
136
|
+
pricing: [".pricing-card", ".pricing-table"],
|
|
137
|
+
};
|
|
138
|
+
const out = {};
|
|
139
|
+
for (const [name, selectors] of Object.entries(families)) {
|
|
140
|
+
out[name] = selectors.flatMap((s) => [...document.querySelectorAll(s)].map(read));
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Run the collector on every route in scope. Compare the result for each family across pages. Any difference in padding, radius, shadow, heading size, or CTA position that is not explained by a named variant is drift; fix the component (not the page) until the next run is clean.
|
|
147
|
+
|
|
148
|
+
A family is drift-free when every same-variant instance produces the same metrics within rounding tolerance.
|
|
149
|
+
|
|
150
|
+
## CSS Patterns That Prevent Drift
|
|
151
|
+
|
|
152
|
+
These patterns reduce the surface area for drift before it starts. Use them on every shared widget. Detailed treatment of layout primitives lives in [responsive.md](responsive.md); detailed treatment of color, typography, and shadow tokens lives in [design.md](design.md). The patterns below are the multi-instance subset.
|
|
153
|
+
|
|
154
|
+
### Tokens before primitives
|
|
155
|
+
|
|
156
|
+
Normalize spacing, color, border, radius, shadow, and type scale tokens before building components. A component that references tokens stays in step with the system; a component that hardcodes values drifts from it.
|
|
157
|
+
|
|
158
|
+
### Stable dimensions for repeated UI
|
|
159
|
+
|
|
160
|
+
Card rows, logo tiles, and grid items must hold a consistent shape regardless of content length.
|
|
161
|
+
|
|
162
|
+
- `min-height` on cards prevents short-content rows from collapsing.
|
|
163
|
+
- `aspect-ratio` on media boxes prevents image jitter.
|
|
164
|
+
- `align-items: stretch` on grid and flex rows keeps siblings the same height.
|
|
165
|
+
- Fixed icon boxes (square wrapper around the SVG) prevent optical mis-centering.
|
|
166
|
+
|
|
167
|
+
### Card grid pattern
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
.card-grid {
|
|
171
|
+
display: grid;
|
|
172
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
173
|
+
gap: 24px;
|
|
174
|
+
align-items: stretch;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.card-grid > * {
|
|
178
|
+
min-width: 0;
|
|
179
|
+
height: 100%;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.card-link {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
min-height: 100%;
|
|
186
|
+
text-decoration: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.card-link:focus-visible,
|
|
190
|
+
.button:focus-visible {
|
|
191
|
+
outline: 3px solid var(--focus-ring-fallback, #4f8cff);
|
|
192
|
+
outline: 3px solid color-mix(in srgb, var(--accent) 45%, white);
|
|
193
|
+
outline-offset: 3px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@media (max-width: 767px) {
|
|
197
|
+
.card-grid {
|
|
198
|
+
grid-template-columns: 1fr;
|
|
199
|
+
gap: 16px;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The `min-width: 0` on grid children is the most-skipped guard against text overflow. Without it, long words break out of the cell and drag the page wide on mobile.
|
|
205
|
+
|
|
206
|
+
`color-mix(in srgb, ...)` is Baseline 2024. Older browsers fall back to the preceding `outline` declaration, so always provide the static-color line first. If the project supports browsers older than the 2024 baseline, drop the `color-mix` line entirely and use a precomputed token (`var(--color-focus-ring)`) instead.
|
|
207
|
+
|
|
208
|
+
### Spacing via gap, not margin stacks
|
|
209
|
+
|
|
210
|
+
Use `gap` for internal spacing on flex and grid containers. Margin stacks accumulate across instances and produce per-page drift; `gap` is owned by the container and stays constant.
|
|
211
|
+
|
|
212
|
+
### Line length via max-width
|
|
213
|
+
|
|
214
|
+
Constrain prose with `max-inline-size: 65ch` (or similar). Cards without measure constraints produce 90-character lines on desktop and 30-character lines on mobile, neither of which reads cleanly.
|
|
215
|
+
|
|
216
|
+
### Full-card anchor, not nested anchors
|
|
217
|
+
|
|
218
|
+
When a card visually behaves as a link, the entire card is the anchor. Nested anchors are invalid HTML and produce inconsistent click areas. If a card needs an inner CTA distinct from the outer link, switch to the button-card pattern: a stretched `::before` pseudo-element on the title link makes the whole card clickable while keeping nested controls separate.
|
|
219
|
+
|
|
220
|
+
### Hover transforms do not stack
|
|
221
|
+
|
|
222
|
+
If the card animates on hover and an inner CTA also animates, the two transforms compose and produce a buggy shift. Pick one layer for the hover transform; suppress the other inside the animated container.
|
|
223
|
+
|
|
224
|
+
### Focus-visible, not focus
|
|
225
|
+
|
|
226
|
+
Use `:focus-visible` so mouse users do not see a ring when clicking but keyboard users always do. The ring must be 2 to 4 px, with 3:1 contrast against both the surface and the resting state. Detailed contrast targets live in [accessibility.md](accessibility.md).
|
|
227
|
+
|
|
228
|
+
### Image loading strategy per role
|
|
229
|
+
|
|
230
|
+
- Hero LCP image: `loading="eager"`, `fetchpriority="high"`, declared `width` and `height`, `srcset` plus `sizes` for responsive widths.
|
|
231
|
+
- Below-the-fold imagery: `loading="lazy"`, `decoding="async"`.
|
|
232
|
+
- Logo tiles and avatars: fixed intrinsic dimensions, consistent ratio across the family.
|
|
233
|
+
|
|
234
|
+
Performance treatment of images and fonts lives in [performance.md](performance.md); the rule above is the multi-instance one.
|
|
235
|
+
|
|
236
|
+
### Visible text is the accessible name
|
|
237
|
+
|
|
238
|
+
Do not hide visible text behind a mismatched `aria-label`. If the visible text names the control well, let it be the accessible name. When a control is icon-only, the `aria-label` matches the visible meaning the icon conveys.
|
|
239
|
+
|
|
240
|
+
## See Also
|
|
241
|
+
|
|
242
|
+
- [audit-workflow.md](audit-workflow.md) for the multi-page audit procedure that builds the widget inventory
|
|
243
|
+
- [defects.md](defects.md) for symptom-to-fix lookup and the canonical geometry sweep
|
|
244
|
+
- [ui-ux.md](ui-ux.md) for state coverage and interaction patterns
|
|
245
|
+
- [accessibility.md](accessibility.md) for focus-visible, accessible names, and contrast rules referenced in the CSS patterns above
|
|
246
|
+
- [responsive.md](responsive.md) for layout primitives
|
|
247
|
+
- [design.md](design.md) for tokens, typography, and shadow language
|