| name | web-accessibility |
| description | Use this skill whenever the user is building or reviewing any web UI. Accessibility is a baseline requirement, not a niche concern. Covers WCAG 2.2 AA, semantic HTML, ARIA patterns, keyboard navigation, focus management, color contrast, touch targets, forms, and testing with axe / screen readers. Trigger when generating any interactive component or page (forms, modals, menus, tables, navigation, layouts), before any "launch" or "go live", during pre-launch checklists, and whenever producing new HTML/JSX markup, even if the user does not explicitly mention a11y or WCAG. Skip for backend-only changes, CLI tools, or non-UI code. |
Web Accessibility Guide
Applies to: Any website or web app | Updated: March 2026
A practical reference for building accessible websites - covering WCAG 2.2 criteria, semantic HTML, ARIA, forms, touch, and testing.
Section 0: Before You Start
Answer these questions before generating accessibility code. Each answer changes which sections apply.
Q: Where are your users?
Default: worldwide - EU/UK requirements (EAA, PSBAR) will be flagged as applicable throughout this guide.
Options: US only | Europe/EU | UK | Worldwide
Q: What kind of product is this?
Default: company/marketing website
Options: Company/marketing website | Online shop/e-commerce | Web app/SaaS | Media/video site | Document tool
Q: Will people use this on mobile phones or tablets as well as desktop?
Default: yes - touch target sizing and pointer accessibility sections apply.
Q: Are you building with a component library?
Default: none
Options: Radix UI | shadcn/ui | MUI | Chakra UI | Other | None - if a library is detected in package.json, note which accessibility features it handles natively so you don't duplicate them.
Q: Do you need to comply with a specific standard for legal or contract reasons?
Default: WCAG 2.2 AA - current best-practice baseline.
Options: WCAG 2.1 AA | WCAG 2.2 AA | Section 508 | EN 301 549 | Not sure
AI assistant: Use these answers to prioritize. For EU products: apply EAA compliance notes. For mobile: apply touch target and pointer sections. If a known accessible component library is detected, note which patterns it handles so you don't duplicate effort.
Contents
- Legal & Compliance
- WCAG Standards Overview
- WCAG 2.2 - What's New
- Semantic HTML
- Keyboard Navigation
- ARIA
- Images & Media
- Forms
- Touch & Pointer
- Color & Contrast
- Focus Management
- Motion & Animation
- Testing
- Anti-Patterns to Flag
- Checklist by Product Type
Legal & Compliance
Accessibility is a legal requirement across most major markets. Non-compliance carries real risk - fines, market access restrictions, and litigation.
| Jurisdiction | Law / Standard | Scope | Notes |
|---|
| EU | European Accessibility Act (EAA) | Private-sector e-commerce, banking, transport, streaming. Enforceable since June 28, 2025. | Standard: EN 301 549 v3.2.1, which references WCAG 2.1 AA; WCAG 2.2 strongly recommended. Non-compliance = market access restrictions. |
| US | ADA Title III | Private-sector websites and apps | ~4,000 - 4,500 lawsuits/year; e-commerce and hospitality are primary targets. Overlay-only solutions have been named in lawsuits as insufficient. |
| US Federal | Section 508 | Federal agencies and contractors | References WCAG 2.0 AA. Agencies procuring software must meet this baseline. |
| UK | Web Accessibility Regulations 2018 + Equality Act 2010 | Public sector: WCAG 2.2 AA required. Private sector: Equality Act applies. | PSBAR (Public Sector Bodies Accessibility Regulations) requires a published accessibility statement. |
Overlay warning: Single-script accessibility overlays (AccessiBe, UserWay, etc.) do not achieve WCAG conformance and have been directly challenged in ADA litigation. Flag this clearly if a user asks about "automatic" or "one-line" accessibility fixes.
WCAG Standards Overview
WCAG (Web Content Accessibility Guidelines) uses three conformance levels:
- Level A - minimum baseline; blocking failures (e.g. images with no
alt)
- Level AA - standard legal requirement across most jurisdictions (e.g. 4.5:1 contrast)
- Level AAA - enhanced; not required site-wide but worth targeting for specific features
Target WCAG 2.2 Level AA as your default baseline.
On WCAG 3.0: It is a research-stage Working Draft with no published adoption timeline. It uses a different scoring model and will not replace WCAG 2.x for the foreseeable future. Do not use it as a current compliance target.
WCAG 2.2 - What's New
WCAG 2.2 was published October 2023. These criteria are not in most existing guides or tools. They represent the highest-value additions to implement.
2.4.11 Focus Appearance (AA)
The focus indicator must meet two conditions simultaneously:
- Area: The focus indicator area must be at least as large as a 2px perimeter outline around the unfocused component.
- Contrast: The color change between focused and unfocused states must have a contrast ratio of at least 3:1.
A 1px dotted border fails both. This goes beyond "don't remove outline."
:focus {
outline: 1px dotted #999;
}
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
2.5.7 Dragging Movements (AA)
Any interaction that uses drag-and-drop, slider, or map pan must have a single-pointer (click/tap) alternative. Drag is not a safe assumption - users with motor disabilities cannot rely on it.
<li>
<span class="drag-handle" aria-hidden="true">⠿</span>
Item label
<button aria-label="Move item up">↑</button>
<button aria-label="Move item down">↓</button>
</li>
2.5.8 Target Size Minimum (AA)
Interactive targets must be at least 24×24 CSS pixels, or have spacing such that the total clickable zone (target + surrounding space) reaches 24×24.
Best practice: 44×44px (Apple HIG), 48×48px (Material Design).
.icon-btn {
min-width: 24px;
min-height: 24px;
}
.icon-btn {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
3.3.8 Accessible Authentication (AA)
Login and signup flows cannot rely solely on a cognitive test (e.g. image CAPTCHA with no alternative). Acceptable alternatives: passkeys, magic link via email, CAPTCHA with an audio alternative, or copy-paste support in the password field.
Do not block clipboard paste in password fields - this breaks password managers and fails this criterion.
3.3.7 Redundant Entry (AA)
Multi-step forms must not ask for the same information twice unless it is essential to re-enter. Auto-populate or display previously entered data.
<label>
<input type="checkbox" id="same-as-billing" />
Same as billing address
</label>
3.2.6 Consistent Help (AA)
If a help mechanism (chat widget, phone number, help link, contact form) appears on more than one page, it must appear in the same relative location on every page.
4.1.1 Parsing - Removed in WCAG 2.2
WCAG 2.2 removed criterion 4.1.1 Parsing. Modern browsers reliably handle malformed HTML; it no longer represents an accessibility barrier. Automated tools that still report 4.1.1 violations are reporting against a deprecated criterion. Remove it from test baselines if you're targeting WCAG 2.2.
Semantic HTML
Semantic HTML is the highest-value accessibility improvement - it is free, requires no ARIA, and works with all assistive technologies.
Page structure
<html lang="en">
<head>
<title>Page Title - Site Name</title>
</head>
<body>
<header>
<nav aria-label="Main">...</nav>
</header>
<main>
<h1>Page Heading</h1>
<section aria-labelledby="section-heading">
<h2 id="section-heading">Section Title</h2>
</section>
</main>
<aside aria-label="Related links">...</aside>
<footer>...</footer>
</body>
For inline content in a different language, add the lang attribute on the element - screen readers use this to switch pronunciation:
<p>The French say <span lang="fr">bonjour</span> as a greeting.</p>
Use the right element
Native HTML elements carry built-in keyboard support, roles, and states. Prefer them over custom implementations. Use <button> for anything that triggers an action, <a href> for navigation - never a <div onclick>.
One <h1> per page; don't skip heading levels. Use headings to convey document structure, not visual size.
Keyboard Navigation
All interactive elements must be reachable and operable with a keyboard alone. Users navigate with Tab, Shift+Tab, Enter, Space, and arrow keys.
Skip navigation link
Provide a "skip to main content" link as the first focusable element - lets keyboard users bypass repeated navigation:
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>
.skip-link {
position: absolute;
top: -100%;
left: 0;
}
.skip-link:focus {
top: 0;
}
Focus order and tabindex
Focus must follow a logical order - top-to-bottom, left-to-right, matching visual layout. Never use tabindex values greater than 0; they override the natural tab order and are nearly impossible to maintain.
<div tabindex="0" role="region" aria-label="Interactive map">...</div>
<button tabindex="3">...</button>
Keyboard interactions for custom components
| Component | Expected keys |
|---|
| Button | Enter, Space to activate |
| Link | Enter to follow |
| Checkbox | Space to toggle |
| Radio group | Arrow keys to move between options |
| Dropdown/Select | Arrow keys to navigate, Enter to select, Escape to close |
| Dialog/Modal | Escape to close; focus trapped inside while open |
| Tabs | Arrow keys to switch tabs |
| Accordion | Enter/Space to expand/collapse |
ARIA
ARIA (Accessible Rich Internet Applications) adds semantic meaning when native HTML elements are insufficient. Use it as a last resort.
Do not use ARIA if a native HTML element already has the semantics you need.
aria-label and aria-labelledby
Use aria-label when there is no visible text label:
<button aria-label="Close dialog">✕</button>
<nav aria-label="Main">...</nav>
<nav aria-label="Breadcrumb">...</nav>
aria-label text must match or begin with the visible button text (WCAG 2.5.3 Label in Name) - otherwise voice control users who say "click Close" cannot activate it.
Use aria-labelledby to reference existing visible text, which avoids duplication:
<section aria-labelledby="billing-heading">
<h2 id="billing-heading">Billing</h2>
</section>
aria-describedby
Points to supplementary description - announced after the element's name and role:
<input type="password" id="pw" aria-describedby="pw-hint" />
<p id="pw-hint">Must be at least 8 characters with one number.</p>
aria-expanded
Toggle between "true" and "false" as the element opens and closes:
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>
aria-hidden
Hides decorative elements from screen readers. Never apply to focusable elements - a hidden-but-focusable element creates an invisible, unannounced interactive target:
<span aria-hidden="true">→</span>
aria-live
Announces dynamic content updates. Use polite for the vast majority of cases - it waits until the screen reader finishes the current sentence before reading the update. Reserve assertive for genuine, time-critical errors that require interrupting the user immediately:
<div aria-live="polite" aria-atomic="true" id="status"></div>
<div aria-live="assertive" id="critical-error"></div>
aria-atomic="true" re-reads the entire region when any part changes, rather than just the changed portion. Use it when partial announcements would be confusing (e.g. "3 results" vs "Showing 3 results for 'shoes'").
Common roles
<div role="alert">Error: email is required.</div>
<div role="status">3 results found.</div
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Delete</h2>
</div>
Images & Media
Alt text
Every <img> must have an alt attribute:
<img src="chart.png" alt="Bar chart showing 40% increase in Q4 sales" />
<img src="divider.png" alt="" />
<a href="/home"><img src="logo.svg" alt="Go to homepage" /></a>
<img src="hero-image-v2-final.jpg" />
SVG
<svg aria-hidden="true" focusable="false">...</svg>
<svg role="img" aria-label="Upload file">...</svg>
Video & audio
- Add captions to all video with speech or meaningful sound (
<track kind="captions">)
- Provide transcripts for audio-only content
- Do not autoplay audio or video with sound
<video controls>
<source src="video.mp4" type="video/mp4" />
<track kind="captions" src="captions.vtt" srclang="en" label="English" default />
</video>
Forms
Forms are one of the most common accessibility failure points.
Labels
Every input needs a visible, associated label. Never use placeholder as a label - it disappears on input and typically fails contrast requirements:
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email" />
<label>
Email address
<input type="email" name="email" autocomplete="email" />
</label>
autocomplete tokens (WCAG 1.3.5 - Identify Input Purpose)
Add autocomplete tokens to personal data fields. This enables password managers, browser autofill, and assistive technology for users with cognitive disabilities:
<input type="text" name="fname" autocomplete="given-name" />
<input type="text" name="lname" autocomplete="family-name" />
<input type="email" name="email" autocomplete="email" />
<input type="tel" name="phone" autocomplete="tel" />
<input type="password" name="password" autocomplete="current-password" />
<input type="password" name="new-pw" autocomplete="new-password" />
Required fields
For native <input> elements, the required attribute is sufficient - it is announced by screen readers and triggers native validation. Add aria-required="true" only for custom components that don't use native form elements:
<label for="name">Full name <span aria-hidden="true">*</span></label>
<input type="text" id="name" required />
<p>Fields marked <span aria-hidden="true">*</span> are required.</p>
<div role="combobox" aria-required="true" ...>...</div>
Error messages
Link errors to their input using aria-describedby. Set aria-invalid="true" when an error exists; remove it when the value becomes valid:
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-error" aria-invalid="true" />
<p id="email-error" role="alert">Enter a valid email address.</p>
Fieldsets for grouped inputs
Group related inputs (radio buttons, checkboxes) with <fieldset> and <legend> - the legend is announced before each option, providing essential context:
<fieldset>
<legend>Preferred contact method</legend>
<label><input type="radio" name="contact" value="email" /> Email</label>
<label><input type="radio" name="contact" value="phone" /> Phone</label>
</fieldset>
Multi-step forms (WCAG 3.3.7)
Don't ask for the same data twice. Pre-populate fields using previously entered values. Show a summary of entered data before the final submit step.
Touch & Pointer
Minimum touch target size (WCAG 2.5.8)
Interactive targets must be at least 24×24 CSS pixels. Best practice is 44×44px:
.btn-icon {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
@media (pointer: coarse) {
.btn-icon {
min-width: 48px;
min-height: 48px;
}
}
pointer: coarse targets touchscreens and devices where the pointing device has limited accuracy. Use it for any touch-specific spacing or sizing overrides.
1.4.13 Content on Hover or Focus
Tooltips, sub-menus, and dropdowns that appear on hover or focus must meet three conditions:
- Dismissable - the user can dismiss the tooltip without moving pointer or focus (Escape key closes it)
- Hoverable - the mouse can move from the trigger into the tooltip without it disappearing
- Persistent - the tooltip stays visible until the user dismisses it, moves focus, or removes the pointer
.trigger:hover .tooltip { display: block; }
.trigger:hover .tooltip,
.tooltip:hover {
display: block;
}
Also expose tooltip content via keyboard/focus - not hover alone.
Color & Contrast
Color alone must never be the only way to convey information - always pair color with text, icons, or patterns.
Contrast ratios (WCAG 2.2 AA)
| Text type | Minimum ratio |
|---|
| Normal text (< 18pt / < 14pt bold) | 4.5:1 |
| Large text (≥ 18pt / ≥ 14pt bold) | 3:1 |
| UI components and graphics | 3:1 |
| Decorative text, disabled UI | No requirement |
<input style="border-color: red" />
<input style="border-color: red" aria-describedby="err" />
<p id="err">Email is required.</p>
Warning: semi-transparent rgba() text
Semi-transparent colors for text are unreliable for contrast compliance. The effective contrast of rgba(R,G,B,0.5) depends on every background layer beneath it, including parent elements, stacked components, and page backgrounds. You cannot evaluate the contrast of a semi-transparent color in isolation.
The problem: rgba(16,185,129,0.5) (50% emerald-500) on white resolves to approximately #87DCCA, which has a contrast ratio around 1.9:1 against white. On a dark background the same token may pass. The color looks "present" visually but fails silently.
Rule: Never use semi-transparent rgba() values for text content. Use fully opaque color tokens instead. If you need a "muted" appearance, pick a specific opaque value and measure its contrast directly.
color: rgba(16, 185, 129, 0.5);
color: rgba(239, 68, 68, 0.5);
color: #059669;
color: #dc2626;
Context-dependent contrast: the same color token can pass or fail
A color that passes contrast on one background will fail on another. This is the most common real-world failure pattern in multi-section layouts where sections use different background colors.
Example: slate-500 (#64748B)
- On white (#FFFFFF): 4.48:1 - passes AA (barely)
- On slate-50 (#F8FAFC): 4.17:1 - fails AA for normal text
- On slate-100 (#F1F5F9): 3.86:1 - fails AA for normal text
Rule: When a color token is used in more than one context, verify contrast against each background it appears on. Do not assume a token that passes on white will pass on tinted section backgrounds.
Tailwind slate palette contrast reference
Measured contrast ratios for slate shades against common backgrounds. AA requires 4.5:1 for normal text, 3:1 for large text and UI components.
| Slate shade | Hex value | On white #FFFFFF | On slate-50 #F8FAFC | On slate-100 #F1F5F9 |
|---|
| slate-400 | #94A3B8 | 2.85:1 - FAIL | 2.65:1 - FAIL | 2.45:1 - FAIL |
| slate-500 | #64748B | 4.48:1 - FAIL (AA) | 4.17:1 - FAIL | 3.86:1 - FAIL |
| slate-600 | #475569 | 6.32:1 - PASS | 5.88:1 - PASS | 5.45:1 - PASS |
| slate-700 | #334155 | 9.52:1 - PASS | 8.86:1 - PASS | 8.20:1 - PASS |
| slate-800 | #1E293B | 13.3:1 - PASS | 12.4:1 - PASS | 11.5:1 - PASS |
| slate-900 | #0F172A | 17.4:1 - PASS | 16.2:1 - PASS | 15.0:1 - PASS |
Key takeaways:
slate-400 and slate-500 fail AA for normal text on all common backgrounds. Use them only for decorative, non-informational content or large text (3:1 threshold).
slate-600 is the minimum safe value for normal text on white and light tinted backgrounds.
- On slate-50 or slate-100 section backgrounds, be especially careful: the effective contrast is lower than on white by about 15-20%.
Brand color accessibility: decorative vs. text tokens
Bright brand colors are optimized for visual identity, not readability. Saturated greens, teals, and ambers that look bold and confident often fail AA contrast for normal-sized text, particularly on light backgrounds.
Example: emerald-500 (#10B981)
- On white: 3.8:1 - FAIL for normal text (passes only for large text/UI components at 3:1)
- On slate-100: approximately 3.5:1 - FAIL
Pattern: two tokens for one brand color
Define separate tokens for text use and decorative use. Never use the decorative/icon token on body-size text without checking contrast.
--color-brand-decorative: #10B981;
--color-brand-text: #059669;
<span className="text-emerald-500">Status: Active</span> {/* 3.8:1 on white - FAIL */}
<span className="text-emerald-600">Status: Active</span> {/* 4.5:1 on white - PASS */}
<span className="bg-emerald-500 text-white rounded px-2">Active</span>
Check every brand color at the size and background where it actually appears. "It looks bold" is not a contrast measurement.
Tools
Focus Management
Visible focus indicator
Never remove focus outlines without a replacement. WCAG 2.4.11 (new in 2.2) requires a minimum 2px perimeter area and 3:1 contrast between focused and unfocused states:
* { outline: none; }
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
border-radius: 2px;
}
Use :focus-visible rather than :focus - it shows the indicator for keyboard users but suppresses it for mouse clicks, matching user expectations.
Dialogs and modals
When a dialog opens, move focus into it. When it closes, return focus to the trigger element. Trap focus within the dialog while it is open:
const trigger = document.getElementById('open-dialog-btn');
const dialog = document.getElementById('dialog');
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
function openDialog() {
dialog.removeAttribute('hidden');
dialog.querySelector(focusableSelectors).focus();
}
function closeDialog() {
dialog.setAttribute('hidden', '');
trigger.focus();
}
Dynamic content
When content updates dynamically (search results, filtered lists, notifications), announce it or move focus:
statusEl.textContent = `${results.length} results found.`;
resultsContainer.setAttribute('tabindex', '-1');
resultsContainer.focus();
Motion & Animation
Some users experience vestibular disorders triggered by motion. Always respect the OS-level reduced-motion preference.
The prefers-reduced-motion media query and the 5-second animation threshold in WCAG 2.2.2 apply specifically to auto-playing, blinking, or scrolling content that runs in parallel with other content (carousels, looping animations, auto-advancing banners). Short CSS transitions (button hover states, accordion expand) are not subject to the 5-second rule.
.animated {
transition: transform 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.animated {
transition: none;
}
}
Provide pause/stop controls for any auto-playing animation or carousel. Do not autoplay video or audio with sound.
Testing
No single tool catches everything. Use this order - each layer catches what the previous misses.
1. Automated scan
Run first - fast and catches obvious violations. Automated tools catch approximately 30 - 40% of WCAG issues.
- axe DevTools (browser extension) - WCAG violations, ARIA errors
- Lighthouse (Chrome DevTools > Lighthouse tab) - accessibility score, summary of issues
- WAVE (browser extension) - visual overlay showing errors, warnings, and structural info
- IBM Equal Access (browser extension) - WCAG 2.2 coverage
Accessibility tree inspection: Chrome DevTools > Elements panel > Accessibility tab shows the computed accessible name, role, and state for any element. Firefox has a dedicated Accessibility Inspector panel. Use these to debug unexpected announcements - more diagnostic than running axe alone.
2. Keyboard-only
Tab through every interactive flow without a mouse:
- Every interactive element must be reachable and operable
- Focus indicator must always be visible
- Test dialogs, dropdowns, date pickers, and all dynamic content
- Verify Escape closes modals and dropdowns
3. Screen reader
Updated pairings (as of 2026):
| Screen reader | Platform | Browser | Free? |
|---|
| NVDA | Windows | Chrome (primary), Firefox | Yes |
| JAWS | Windows | Chrome | No - most common enterprise SR |
| Narrator | Windows | Edge | Yes (built-in) |
| VoiceOver | macOS / iOS | Safari | Yes (built-in) |
| TalkBack | Android | Chrome | Yes (built-in) |
Basic test: navigate by headings (H in NVDA/JAWS), by landmarks (D), and by form elements (F). Verify every announcement makes sense without visual context.
4. Zoom
- 200% - no content should overflow or become inaccessible
- 400% - content must reflow to a single column without horizontal scrolling (WCAG 1.4.10 Reflow)
5. Text spacing
Apply the WCAG 1.4.12 text spacing bookmarklet (sets line-height: 1.5, letter-spacing: 0.12em, word-spacing: 0.16em, paragraph-spacing: 2em). No content should overlap or be clipped.
Bookmarklet source: stevefaulkner's text spacing bookmarklet
6. Color and vision
- Grayscale filter: Chrome DevTools > Rendering > Emulate vision deficiencies > Achromatopsia - verify no information is lost
- Also test: Deuteranopia (red/green), Protanopia
- Verify no information relies on color alone
Anti-Patterns to Flag
Flag these actively when encountered in code review or generation:
- Accessibility overlay scripts as sole compliance strategy - overlays do not achieve WCAG conformance and have been challenged in ADA litigation. Mention this explicitly.
aria-label that doesn't match visible button text - breaks voice control (WCAG 2.5.3). The label must match or begin with the visible text so "click Submit" works.
aria-hidden="true" on focusable elements - creates a keyboard trap: the element receives focus but is invisible to screen readers.
tabindex values greater than 0 - overrides the natural tab order and becomes unmanageable.
- Hover-only content with no keyboard/focus equivalent - fails WCAG 2.1.1 and 1.4.13.
- Infinite scroll with no keyboard access to footer content - users cannot reach content below the scroll boundary. Provide a "Load more" button or pagination.
- Custom date pickers with no keyboard support - implement full arrow-key navigation or use a native
<input type="date">.
aria-required="true" on native <input required> - redundant in modern browsers; keep only required.
- Placeholder text as the only label - disappears when the user types; fails 1.3.1 and 2.4.6.
- Blocking clipboard paste in password fields - breaks password managers and fails WCAG 3.3.8.
- Semi-transparent rgba() colors for text - the effective contrast depends on all stacked backgrounds and cannot be measured from the color value alone. Use fully opaque tokens for any text content and measure the resulting rendered color against its background.
- Mid-gray palette values (slate-400, slate-500) on light section backgrounds - slate-500 (#64748B) achieves only 4.48:1 on white (barely failing AA) and drops further on tinted backgrounds such as slate-50 (4.17:1) or slate-100 (3.86:1). Use slate-600 or darker for normal text on any light background. Flag all uses of slate-400 and slate-500 on text content for contrast verification.
Checklist by Product Type
All products (universal baseline)
Online shop / e-commerce (add)
Web app / SaaS (add)
Media / video site (add)
Mobile / touch (add when mobile applies)