feat: refresh shared skills catalog
This commit is contained in:
1 parent
98bb35e6fb
commit
418792d1ec
49 files changed
+2273
-2390
No files matched your search
@@ -1,6 +1,7 @@
|
||||
# WCAG 2.1 AA Accessibility Audit Guide
|
||||
# Accessibility audit reference
|
||||
|
||||
Comprehensive checklist for building accessible web interfaces. Every requirement maps to WCAG 2.1 Level AA success criteria.
|
||||
Select relevant checks for the changed flow. This reference mixes WCAG requirements
|
||||
with stricter design recommendations; those recommendations are not AA gates.
|
||||
|
||||
---
|
||||
|
||||
@@ -141,8 +142,11 @@ The first rule of ARIA: do not use ARIA if a native HTML element provides the be
|
||||
}
|
||||
```
|
||||
|
||||
- Focus indicators must meet 3:1 contrast ratio against adjacent colors (WCAG 2.4.11)
|
||||
- Minimum focus indicator area: at least 2px perimeter around the component
|
||||
- Keep focus visible and not entirely obscured by authored content
|
||||
([WCAG 2.2 AA 2.4.11](https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html)).
|
||||
- A 2px-perimeter area and 3:1 focused/unfocused contrast are stronger
|
||||
[AAA focus-appearance criteria](https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html),
|
||||
not the definition of AA 2.4.11. Apply non-text contrast where required.
|
||||
|
||||
### Modal Focus Trapping
|
||||
|
||||
@@ -300,18 +304,14 @@ panel: id="panel-id", role="region", aria-labelledby="trigger-id"
|
||||
|
||||
## 7. prefers-reduced-motion
|
||||
|
||||
### Global Reset
|
||||
### Targeted motion reduction
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
html { scroll-behavior: auto; }
|
||||
/* Apply to components with nonessential movement; keep state feedback. */
|
||||
.decorative-motion { animation: none; }
|
||||
.moving-control { transition-property: color, background-color, opacity; }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -402,7 +402,7 @@ npx playwright test --project=accessibility
|
||||
| `aria-hidden="true"` on focusable elements | Remove from tab order or remove `aria-hidden` |
|
||||
| Missing `lang` attribute on `<html>` | Set `<html lang="en">` (or appropriate language code) |
|
||||
| Autoplaying video/audio with sound | Require user interaction to start, or mute by default with controls |
|
||||
| Tiny tap targets on mobile | Minimum 44x44 CSS pixels for touch targets |
|
||||
| Tiny tap targets on mobile | Aim for 44px touch targets; AA 2.5.8 minimum is 24px with exceptions |
|
||||
| Using `title` attribute as primary label | `title` is unreliable; use `aria-label` or visible `<label>` |
|
||||
| Links that say "click here" or "read more" | Descriptive link text: "Read the accessibility guide" |
|
||||
| Missing form error summary | On submit failure, show summary of all errors at top of form |
|
||||
|
||||
@@ -526,11 +526,12 @@ Use the playback speed controls (25%, 10%) for detailed inspection.
|
||||
### Real device testing
|
||||
|
||||
Touch interactions feel completely different on a real phone versus a trackpad simulator.
|
||||
Always test gestures, drag interactions, and spring animations on physical devices.
|
||||
For gesture-sensitive changes, test on a physical device when available; otherwise
|
||||
use the available simulator/emulator and state the verification limit.
|
||||
|
||||
### Fresh eyes check
|
||||
|
||||
Review animations with fresh eyes the next day. What felt right at 11pm during
|
||||
For a dedicated polish pass, a later review can help. What felt right at 11pm during
|
||||
development often feels too fast, too slow, or too dramatic the next morning.
|
||||
|
||||
### The checklist
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
# Design recipes
|
||||
|
||||
Optional examples for new designs. Existing components, tokens, user preferences,
|
||||
and platform conventions override these recipes. Aesthetic suggestions are not
|
||||
accessibility requirements or completion gates. Read only relevant sections.
|
||||
|
||||
### 2. The Priority Stack
|
||||
|
||||
When implementing UI, work through these priorities in order. Higher priorities are non-negotiable; lower priorities are polish that compounds quality.
|
||||
|
||||
| Priority | Level | What It Means |
|
||||
|----------|-------|---------------|
|
||||
| **Accessibility** | CRITICAL | Contrast 4.5:1, keyboard nav, ARIA semantics, visible focus rings. Ship nothing that excludes users. |
|
||||
| **Performance** | HIGH | WebP/AVIF images, lazy loading below fold, CLS < 0.1, transform-only animations on the compositor thread. |
|
||||
| **Typography** | HIGH | Font smoothing, text-wrap balance/pretty, tabular-nums for data, 65ch max line length. |
|
||||
| **Layout & Spatial** | HIGH | 4/8px grid, concentric border radius, optical alignment over geometric. |
|
||||
| **Color & Theme** | MEDIUM | HSL custom properties, semantic tokens, dark mode pairs tested separately. |
|
||||
| **Motion & Interaction** | MEDIUM | Frequency-based animation decisions, 150-300ms durations, ease-out default. |
|
||||
| **Polish & Details** | LOW | Layered shadows over borders, press feedback on buttons, staggered enter animations. |
|
||||
|
||||
Never skip a CRITICAL/HIGH item to chase a LOW item. A beautifully animated button that fails keyboard navigation is a net negative.
|
||||
|
||||
---
|
||||
|
||||
### 3. Aesthetic Direction
|
||||
|
||||
For a new design, choose a direction suited to the audience and task. The most common failure mode in AI-generated UI is convergence on the same safe, forgettable look.
|
||||
|
||||
### Pick a Tone
|
||||
|
||||
Choose one and commit fully:
|
||||
|
||||
- **Brutally minimal** — generous whitespace, monospace type, stark contrast, near-zero decoration
|
||||
- **Maximalist chaos** — layered textures, clashing type scales, dense information, intentional visual noise
|
||||
- **Retro-futuristic** — CRT glow effects, monospace terminals, scan lines, neon on dark
|
||||
- **Organic / natural** — earth tones, rounded shapes, paper textures, hand-drawn accents
|
||||
- **Luxury / refined** — serif headlines, muted palettes, ample negative space, subtle gold or cream accents
|
||||
- **Editorial / magazine** — dramatic type hierarchy, full-bleed imagery, grid-breaking layouts
|
||||
- **Playful / bold** — bright primaries, chunky borders, exaggerated shadows, bouncy motion
|
||||
|
||||
### Match Complexity to Vision
|
||||
|
||||
Maximalist design demands elaborate code — layered backgrounds, complex grid structures, multiple font stacks. Minimalist design demands surgical precision — every pixel of spacing matters more when there's nothing to hide behind.
|
||||
|
||||
### Visual Texture
|
||||
|
||||
Add depth through: gradient meshes, noise/grain overlays (`filter: url(#noise)`), layered transparencies, subtle background patterns, duotone image treatments.
|
||||
|
||||
Use existing project tokens and components even without a DESIGN.md.
|
||||
|
||||
---
|
||||
|
||||
### 4. Typography Essentials
|
||||
|
||||
Typography is the single highest-leverage design element. Get it right and mediocre layouts still feel good. Get it wrong and nothing else saves it.
|
||||
|
||||
### Root Setup
|
||||
|
||||
```css
|
||||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
```
|
||||
|
||||
Apply font smoothing to the root layout. On macOS, the default sub-pixel rendering makes text appear heavier than the designer intended.
|
||||
|
||||
### Text Wrapping
|
||||
|
||||
```css
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
p, li, dd, blockquote {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
```
|
||||
|
||||
`balance` distributes heading lines evenly. `pretty` avoids orphaned words in body text.
|
||||
|
||||
### Numeric Display
|
||||
|
||||
```css
|
||||
.data-value, .price, .counter, [data-numeric] {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
```
|
||||
|
||||
Use `tabular-nums` for any number that updates dynamically — prices, counters, table columns. Without it, layout shifts as digit widths change.
|
||||
|
||||
### Scale and Rhythm
|
||||
|
||||
- **Base size**: 16px minimum for body text. Never go below 14px for any readable content.
|
||||
- **Line height**: 1.5-1.75 for body text, 1.1-1.3 for large headings.
|
||||
- **Max line length**: `max-width: 65ch` for body text. Long lines destroy readability.
|
||||
- **Type scale**: Pick a consistent scale and stick to it: 12 / 14 / 16 / 18 / 24 / 32 / 48 / 64.
|
||||
|
||||
### Font Pairing
|
||||
|
||||
Pair a distinctive display font with a refined body font. The display font carries personality; the body font carries readability. Use `font-weight` for hierarchy within a family:
|
||||
|
||||
- **Headings**: 600-700 (semibold to bold)
|
||||
- **Body**: 400 (regular)
|
||||
- **Labels / UI**: 500 (medium)
|
||||
|
||||
Always include font stack fallbacks:
|
||||
|
||||
```css
|
||||
--font-display: "Instrument Serif", "Georgia", serif;
|
||||
--font-body: "Söhne", "Helvetica Neue", sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Color & Theme
|
||||
|
||||
### HSL Custom Properties (shadcn Pattern)
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
Define semantic tokens: primary, secondary, destructive, muted, accent, background, foreground. Reference colors by semantic name — never hardcode hex values in components.
|
||||
|
||||
### Dark Mode
|
||||
|
||||
```css
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
/* ... desaturated, lighter tonal variants — NOT simply inverted */
|
||||
}
|
||||
```
|
||||
|
||||
Dark mode is not "invert colors." Use desaturated, lighter tonal variants. Backgrounds go dark but not pure black (`#000`). Text goes light but not pure white (`#fff`). Test contrast separately for dark mode — what passes in light may fail in dark.
|
||||
|
||||
### Contrast Requirements
|
||||
|
||||
- **WCAG AA minimum**: 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+ regular)
|
||||
- Never convey information by color alone — always pair with an icon, label, or pattern
|
||||
- Test with browser devtools contrast checker or axe-core
|
||||
|
||||
### Color Confidence
|
||||
|
||||
Dominant colors with sharp accents outperform timid, evenly-distributed palettes. Pick one or two hero colors and let the rest of the palette recede. A confident palette has clear hierarchy; an uncertain palette spreads color evenly and feels flat.
|
||||
|
||||
---
|
||||
|
||||
## 6. Spatial Design
|
||||
|
||||
### Concentric Border Radius
|
||||
|
||||
This is the single most common thing that makes nested UI elements feel "off":
|
||||
|
||||
```
|
||||
outer_radius = inner_radius + padding
|
||||
```
|
||||
|
||||
```css
|
||||
/* Correct: concentric */
|
||||
.card { border-radius: 16px; padding: 8px; }
|
||||
.card-inner { border-radius: 8px; } /* 16 - 8 = 8 */
|
||||
|
||||
/* Wrong: same radius on parent and child */
|
||||
.card { border-radius: 12px; }
|
||||
.card-inner { border-radius: 12px; } /* Looks bloated */
|
||||
```
|
||||
|
||||
When geometric centering looks off, align optically. Play/pause icons, dropdown carets, and asymmetric glyphs often need 1-2px manual nudges to look centered.
|
||||
|
||||
### Shadows Over Borders
|
||||
|
||||
Layer multiple transparent `box-shadow` values for natural depth instead of using borders:
|
||||
|
||||
```css
|
||||
.elevated {
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.04),
|
||||
0 2px 4px rgba(0, 0, 0, 0.04),
|
||||
0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
```
|
||||
|
||||
Multiple shadows at different spreads mimic how light works. A single hard shadow looks artificial.
|
||||
|
||||
### Image Outlines
|
||||
|
||||
Add a subtle inset outline to images and media for consistent depth against varied backgrounds:
|
||||
|
||||
```css
|
||||
img, video {
|
||||
outline: 1px solid rgba(0, 0, 0, 0.06);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
```
|
||||
|
||||
### Spacing Scale
|
||||
|
||||
Use a 4px / 8px base incremental system. A 4px-based scale is one possible default:
|
||||
|
||||
`4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 / 128`
|
||||
|
||||
### Hit Areas
|
||||
|
||||
Aim for comfortable 44x44px touch targets where practical. If the visual element is smaller, extend the hit area with a pseudo-element:
|
||||
|
||||
```css
|
||||
.small-button::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -8px;
|
||||
}
|
||||
```
|
||||
|
||||
### Z-Index Scale
|
||||
|
||||
Define a layered scale and never use arbitrary values:
|
||||
|
||||
```css
|
||||
--z-base: 0;
|
||||
--z-dropdown: 10;
|
||||
--z-sticky: 20;
|
||||
--z-overlay: 40;
|
||||
--z-modal: 100;
|
||||
--z-toast: 1000;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Motion & Interaction
|
||||
|
||||
### The Frequency-Based Decision Framework
|
||||
|
||||
This is the most important mental model for animation decisions:
|
||||
|
||||
| Frequency | Examples | Animation |
|
||||
|-----------|----------|-----------|
|
||||
| **100+ times/day** | Keyboard shortcuts, command palette actions, tab switches | **None.** Zero animation. Instant. |
|
||||
| **Tens of times/day** | Hover effects, list item navigation, toggles | **Remove or drastically reduce.** 50-100ms max. |
|
||||
| **Occasional** | Modals, drawers, toasts, page transitions | **Standard animation.** 150-300ms. |
|
||||
| **Rare / first-time** | Onboarding, celebrations, empty states | **Can add delight.** 300-500ms, more elaborate. |
|
||||
|
||||
High-frequency animations feel sluggish. Low-frequency animations without motion feel jarring. Match the animation budget to usage frequency.
|
||||
|
||||
### Custom Easing Curves
|
||||
|
||||
Custom easing curves are an option when motion helps the interaction:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
|
||||
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
|
||||
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
```
|
||||
|
||||
### Duration Guide
|
||||
|
||||
| Element | Duration |
|
||||
|---------|----------|
|
||||
| Buttons, toggles | 100-160ms |
|
||||
| Tooltips | 125-200ms |
|
||||
| Dropdowns, popovers | 150-250ms |
|
||||
| Modals, drawers | 200-500ms |
|
||||
| Page transitions | 250-400ms |
|
||||
|
||||
Keep motion responsive; choose duration and easing for the interaction.
|
||||
|
||||
### Enter/Exit Asymmetry
|
||||
|
||||
Exits should be softer and faster than enters. An enter animation at 250ms should have its exit at 150-200ms.
|
||||
|
||||
### Split and Stagger Enter Animations
|
||||
|
||||
When multiple elements enter the viewport, stagger them by semantic chunks with ~50-100ms delay:
|
||||
|
||||
```css
|
||||
.stagger-item {
|
||||
animation: fadeSlideIn 300ms var(--ease-out) both;
|
||||
}
|
||||
.stagger-item:nth-child(1) { animation-delay: 0ms; }
|
||||
.stagger-item:nth-child(2) { animation-delay: 60ms; }
|
||||
.stagger-item:nth-child(3) { animation-delay: 120ms; }
|
||||
```
|
||||
|
||||
### Scale Animations
|
||||
|
||||
Never animate from `scale(0)`. Start from `scale(0.9)` or higher, combined with opacity:
|
||||
|
||||
```css
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
```
|
||||
|
||||
### Press Feedback
|
||||
|
||||
Optional press feedback, when consistent with the design and reduced-motion policy:
|
||||
|
||||
```css
|
||||
button:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
```
|
||||
|
||||
### Interruptibility
|
||||
|
||||
Use CSS transitions (not keyframe animations) for interactive state changes. Transitions can be interrupted mid-way; keyframes cannot. This matters for hover states, toggles, and any element the user might interact with rapidly.
|
||||
|
||||
### Popover Origin
|
||||
|
||||
Make popovers transform-origin aware — they should grow from their trigger element, not from center. Exception: modals always originate from center.
|
||||
|
||||
### Tooltip Hover Delay
|
||||
|
||||
Skip the tooltip delay on subsequent hovers. If the user has already waited for one tooltip, show the next one immediately.
|
||||
|
||||
### Reduced Motion
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
.decorative-motion { animation: none; }
|
||||
.moving-control { transition-property: color, background-color, opacity; }
|
||||
}
|
||||
```
|
||||
|
||||
Respect `prefers-reduced-motion`. Reduce animations — don't eliminate opacity and color transitions entirely, as those provide important feedback.
|
||||
|
||||
### Hover Gate
|
||||
|
||||
Gate hover animations behind a media query so touch devices don't trigger stuck hover states:
|
||||
|
||||
```css
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover { transform: translateY(-2px); }
|
||||
}
|
||||
```
|
||||
|
||||
> Reference `animation-playbook.md` for deep dives on spring physics, gesture-driven animation, and complex choreography.
|
||||
|
||||
---
|
||||
|
||||
### 8. Component Craft
|
||||
|
||||
### Primitives
|
||||
|
||||
In a React project already using Radix, its primitives can provide accessible foundations. Use CVA (class-variance-authority) for type-safe component variants:
|
||||
|
||||
```tsx
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
sm: "h-9 px-3 text-sm",
|
||||
default: "h-10 px-4 py-2",
|
||||
lg: "h-11 px-8 text-lg",
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: "default", size: "default" },
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Button
|
||||
|
||||
- Scale on press (`transform: scale(0.97)` on `:active`)
|
||||
- Visible focus ring (never `outline: none` without replacement)
|
||||
- Loading state with spinner replacing label, maintaining button dimensions
|
||||
- Disabled state at `opacity: 0.5` with `pointer-events: none`
|
||||
|
||||
### Card
|
||||
|
||||
- Concentric border radius between card and inner elements
|
||||
- Layered shadows (not borders) for depth
|
||||
- Hover state: subtle elevation change (`translateY(-1px)` + shadow increase)
|
||||
|
||||
### Dialog / Modal
|
||||
|
||||
- Focus trap (keyboard cannot escape to elements behind)
|
||||
- ESC to close, click outside overlay to close
|
||||
- `transform-origin: center`, fade + scale enter animation
|
||||
- `aria-modal="true"`, `role="dialog"`, `aria-labelledby`
|
||||
|
||||
### Form
|
||||
|
||||
- Visible labels always — never placeholder-only inputs
|
||||
- Error messages near the field with `aria-live="polite"` for screen readers
|
||||
- Progressive disclosure: show advanced fields only when needed
|
||||
- Use the project's form and runtime validation libraries
|
||||
|
||||
### Theming
|
||||
|
||||
Use the project's semantic color tokens. Wrap client-interactive components in server components for Next.js App Router compatibility.
|
||||
|
||||
> Reference `component-patterns.md` for the full component catalog with copy-paste implementations.
|
||||
|
||||
---
|
||||
|
||||
## 9. Accessibility Essentials
|
||||
|
||||
### Semantic HTML First
|
||||
|
||||
Use `<button>`, `<nav>`, `<main>`, `<header>`, `<footer>`, `<article>`, `<section>` before reaching for ARIA. A `<button>` gives you keyboard handling, focus management, and screen reader semantics for free. A `<div onClick>` gives you none of that.
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- **Tab / Shift+Tab**: move between focusable elements
|
||||
- **Enter / Space**: activate buttons and links
|
||||
- **Arrow keys**: navigate within lists, menus, tabs, radio groups
|
||||
- **Escape**: close modals, popovers, dropdowns
|
||||
- **Home / End**: jump to first/last item in lists
|
||||
|
||||
### Focus Management
|
||||
|
||||
- Visible focus rings on all interactive elements — NEVER use `outline: none` without a replacement
|
||||
- Trap focus inside modals (Tab wraps within the modal, not behind it)
|
||||
- Restore focus to the trigger element when a modal/popover closes
|
||||
- Use `focus-visible` to show rings only for keyboard users, not mouse clicks:
|
||||
|
||||
```css
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
### ARIA Attributes
|
||||
|
||||
- `aria-label` for icon-only buttons: `<button aria-label="Close menu">X</button>`
|
||||
- `aria-labelledby` to associate headings with sections
|
||||
- `aria-describedby` to link help text or error messages to inputs
|
||||
- `aria-live="polite"` for dynamic content updates (toast messages, form errors)
|
||||
- `aria-hidden="true"` for decorative elements (icons next to text labels)
|
||||
- `aria-expanded` for toggleable elements (dropdowns, accordions)
|
||||
|
||||
### Color and Contrast
|
||||
|
||||
- WCAG AA: 4.5:1 for normal text, 3:1 for large text
|
||||
- Never use color as the sole indicator — pair with icons, text, or patterns
|
||||
- Test in both light and dark modes
|
||||
|
||||
### Images and Media
|
||||
|
||||
- Descriptive `alt` text for meaningful images: `alt="Dashboard showing 23% revenue growth"`
|
||||
- Empty `alt=""` for purely decorative images
|
||||
- Captions for video, transcripts for audio
|
||||
|
||||
### Navigation Aids
|
||||
|
||||
- **Skip link**: first focusable element, hidden until focused:
|
||||
|
||||
```html
|
||||
<a href="#main-content" class="sr-only focus:not-sr-only">
|
||||
Skip to main content
|
||||
</a>
|
||||
```
|
||||
|
||||
- **Heading hierarchy**: sequential h1 through h6, no level skips. One `<h1>` per page.
|
||||
|
||||
### Touch Targets
|
||||
|
||||
- Aim for 44x44px touch targets; WCAG 2.2 AA minimum is 24px with exceptions
|
||||
- 8px minimum spacing between adjacent touch targets
|
||||
- Extend small visual elements with invisible padding or pseudo-elements
|
||||
|
||||
### Testing
|
||||
|
||||
- **Automated**: relevant axe-core checks when available; a Lighthouse score alone does not prove conformance
|
||||
- **Manual**: full keyboard-only navigation test
|
||||
- **Screen reader**: test with VoiceOver (macOS) or NVDA (Windows)
|
||||
- **Visual**: zoom to 200%, check nothing breaks or overlaps
|
||||
|
||||
> Reference `accessibility-checklist.md` for the full audit guide with pass/fail criteria.
|
||||
|
||||
---
|
||||
@@ -1,6 +1,8 @@
|
||||
# Pre-Delivery Review Checklist
|
||||
|
||||
Extended 30-item checklist for UI implementation quality. Run through this before marking any UI task as complete.
|
||||
Optional polish checklist. Select items relevant to the changed flow. Existing design
|
||||
conventions override aesthetic suggestions; accessibility requirements remain applicable.
|
||||
Do not run every item for a small edit.
|
||||
|
||||
## Typography (6 items)
|
||||
|
||||
|
||||
Reference in new issue
Block a user