* feat(instructions): update security, a11y, and performance to 2025-2026 standards Security: OWASP 2025 (55 anti-patterns, AI/LLM section, 6 frameworks) Accessibility: WCAG 2.2 AA (38 anti-patterns, legal context EAA/ADA, 4 frameworks) Performance: CWV (50 anti-patterns, Next.js 16, Angular 20, modern APIs) * fix(instructions): use globalThis.scheduler to prevent ReferenceError Access scheduler via globalThis to safely handle environments where the Scheduling API is not declared as a global variable. * fix(instructions): correct regex patterns and harden SSRF example - AU1: anchor jwt.verify lookahead inside parentheses - AU2: anchor jwt.sign lookahead, add expiresIn alternative - AU7: fix greedy .* before negative lookahead in OAuth state check - I5: resolve all DNS records, add TOCTOU production note - K2: add closing delimiters and multi-digit support to tabindex regex * fix(instructions): enhance SSRF IP validation with IPv4-mapped IPv6 Normalize IPv4-mapped IPv6 addresses (::ffff:127.0.0.1) before checking private ranges, preventing bypass via mapped addresses. * fix(instructions): add noscript fallback for deferred CSS pattern Without JS, the media="print" + onload pattern leaves the stylesheet inactive. The noscript tag loads it normally when JS is disabled. * fix(instructions): add execFileSync to I3 command injection detection The BAD example uses execFileSync but the regex only matched exec, execSync, and execFile — missing the sync variant. * fix(instructions): cover full IPv6 link-local range in SSRF check fe80::/10 spans fe80-febf (fe8*, fe9*, fea*, feb*). Previous regex only matched fe80::. Also use normalized variable for consistency. * fix(instructions): adjust SSRF wording and downgrade reduced-motion severity - SSRF: replace "full DNS/IP validation" with accurate wording that acknowledges TOCTOU limitation - V5: downgrade prefers-reduced-motion from IMPORTANT to SUGGESTION, remove 2.2.2 (A) reference since it's an AAA enhancement * fix(instructions): rename AU4 heading to include SHA-256 The heading said "Weak Password Hash (MD5/SHA1)" but the detection regex and BAD example both use SHA-256. Renamed to "Fast Hash for Passwords" which better describes the actual anti-pattern. * fix(instructions): clarify WCAG 2.2 SC 4.1.1 status as obsolete SC 4.1.1 Parsing is still present in the WCAG 2.2 spec but marked as obsolete (always satisfied). Changed wording from "removed" to "obsolete" for accuracy. * fix(instructions): rename I1 example vars to avoid TS redeclaration Copy-pasting the I1 SQL injection example as a single block failed with a TypeScript redeclaration error because both BAD and GOOD snippets used `const result`. Rename to `unsafeResult`/`safeResult` so the block remains copy-pasteable into a single scope. * fix(instructions): migrate I3 example to async execFile with bounds The I3 command injection example used `execFileSync` in both BAD and GOOD paths, which (a) redeclared `const output` in the same block and (b) blocks the Node event loop in server handlers, amplifying DoS impact. Switch the GOOD/BEST paths to a promisified `execFile` call with explicit `timeout` and `maxBuffer` bounds, and rename variables to `unsafeOutput`/`safeOutput` so the snippet stays copy-pasteable. Add a trailing note recommending async child_process APIs for server code. * fix(instructions): align AU6 heading with session fixation example The AU6 heading claimed "Session Not Invalidated on Password Change" but the mitigation example showed `req.session.regenerate`, which is the canonical defense against session fixation on login rather than bulk invalidation after a credential change. Rename the anti-pattern to "Missing Session Regeneration on Login (Session Fixation)" so it matches the example, and add a trailing note pointing to the complementary practice of invalidating other active sessions for the user on password change (e.g., via a `tokenVersion` counter). * fix(instructions): make L1 critical CSS pattern CSP-compatible The L1 "GOOD" snippet relied on an inline `onload="this.media='all'"` handler on a `<link>` tag. Under a strict CSP that disallows `'unsafe-inline'` / `script-src-attr 'unsafe-inline'`, inline event handlers are blocked, so the stylesheet would never activate and users would hit a styling regression. Replace the pattern with build-time critical CSS extraction (Critters/Beasties/Next.js `optimizeCss`) plus a normal `<link rel="preload" as="style">` and standard `<link rel="stylesheet">`. Add a trailing note explaining why the older inline-onload trick breaks under strict CSP and how to defer non-critical CSS with an external script when deferral is truly needed.
26 KiB
applyTo, description
| applyTo | description |
|---|---|
| ** | Comprehensive web accessibility standards based on WCAG 2.2 AA, with 38+ anti-patterns, legal enforcement context (EAA, ADA Title II), WAI-ARIA patterns, and framework-specific fixes for modern web frameworks and libraries. |
Accessibility Standards
Comprehensive accessibility rules for web application development. Every anti-pattern includes a severity classification, detection method, WCAG 2.2 reference, and corrective code examples.
Severity levels:
- CRITICAL — Users cannot access content at all. Must be fixed before merge.
- IMPORTANT — Significant barrier for assistive technology users. Fix in same sprint.
- SUGGESTION — Improves usability for assistive technology. Plan for a future iteration.
WCAG 2.2 Quick Reference (AA Level)
Perceivable
| Criterion | Level | Summary |
|---|---|---|
| 1.1.1 Non-text Content | A | All non-text content has a text alternative. Decorative images use alt="". |
| 1.2.1 Audio/Video-only | A | Provide transcript (audio) or text alternative (video). |
| 1.2.2 Captions (Prerecorded) | A | All prerecorded video has synchronized captions. |
| 1.3.1 Info and Relationships | A | Structure (headings, lists, tables, labels, landmarks) programmatically conveyed. |
| 1.3.2 Meaningful Sequence | A | DOM reading order matches visual order. |
| 1.3.3 Sensory Characteristics | A | Instructions don't rely solely on shape, size, position, or sound. |
| 1.3.4 Orientation | AA | Content not restricted to single orientation unless essential. |
| 1.3.5 Identify Input Purpose | AA | Input fields have autocomplete attributes for user data (name, email, tel). |
| 1.4.1 Use of Color | A | Color is not the only means of conveying info. |
| 1.4.3 Contrast (Minimum) | AA | Text: 4.5:1 normal, 3:1 large (18pt / 14pt bold). |
| 1.4.4 Resize Text | AA | Text resizable to 200% without loss of content. |
| 1.4.10 Reflow | AA | Content reflows at 320px CSS width (no horizontal scroll). |
| 1.4.11 Non-text Contrast | AA | UI components and graphics: 3:1 against adjacent colors. |
| 1.4.12 Text Spacing | AA | No loss of content with overridden line-height (1.5x), spacing. |
| 1.4.13 Content on Hover/Focus | AA | Tooltips: dismissible, hoverable, persistent. |
Operable
| Criterion | Level | Summary |
|---|---|---|
| 2.1.1 Keyboard | A | All functionality operable via keyboard. |
| 2.1.2 No Keyboard Trap | A | User can navigate away from any component using keyboard. |
| 2.2.1 Timing Adjustable | A | Time limits can be extended or disabled. |
| 2.2.2 Pause, Stop, Hide | A | Auto-updating content can be paused. |
| 2.3.1 Three Flashes | A | No content flashes more than 3 times per second. |
| 2.4.1 Bypass Blocks | A | Skip link to bypass repeated navigation. |
| 2.4.2 Page Titled | A | Pages have descriptive <title>. |
| 2.4.3 Focus Order | A | Focus order preserves meaning and operability. |
| 2.4.4 Link Purpose | A | Link purpose determinable from text or context. |
| 2.4.6 Headings and Labels | AA | Headings and labels describe topic or purpose. |
| 2.4.7 Focus Visible | AA | Keyboard focus indicator is visible. |
| 2.4.11 Focus Not Obscured | AA | Focused element not entirely hidden by sticky headers/footers. (New in 2.2) |
| 2.5.1 Pointer Gestures | A | Multi-point gestures have single-pointer alternative. |
| 2.5.2 Pointer Cancellation | A | Activation on up-event, not down-event. |
| 2.5.3 Label in Name | A | Accessible name contains the visible label text. |
| 2.5.4 Motion Actuation | A | Device motion has UI alternative and can be disabled. |
| 2.5.7 Dragging Movements | AA | Drag-and-drop has click/tap alternative. (New in 2.2) |
| 2.5.8 Target Size (Minimum) | AA | Touch targets at least 24x24 CSS px. (New in 2.2) |
Understandable
| Criterion | Level | Summary |
|---|---|---|
| 3.1.1 Language of Page | A | <html lang="..."> set correctly. |
| 3.1.2 Language of Parts | AA | Content in different language marked with lang attribute. |
| 3.2.1 On Focus | A | Focus doesn't trigger unexpected context change. |
| 3.2.2 On Input | A | Changing input doesn't auto-trigger unexpected context change. |
| 3.2.6 Consistent Help | A | Help mechanisms in same relative order across pages. (New in 2.2) |
| 3.3.1 Error Identification | A | Errors described to user in text. |
| 3.3.2 Labels or Instructions | A | Labels or instructions provided for user input. |
| 3.3.3 Error Suggestion | AA | Suggest corrections for detected errors. |
| 3.3.4 Error Prevention | AA | Submissions are reversible, checked, or confirmed. |
| 3.3.7 Redundant Entry | A | Don't re-ask for info already provided in same process. (New in 2.2) |
| 3.3.8 Accessible Authentication (Minimum) | AA | No cognitive function test (puzzle CAPTCHA). Allow paste and autofill. (New in 2.2) |
Robust
| Criterion | Level | Summary |
|---|---|---|
| 4.1.2 Name, Role, Value | A | All UI components have accessible name, role, and state. |
| 4.1.3 Status Messages | AA | Status messages announced by screen readers without receiving focus. |
Note: 4.1.1 Parsing is obsolete in WCAG 2.2 (always satisfied). Issues it covered are now addressed by 1.3.1 and 4.1.2.
New AAA criteria in 2.2 (not required for AA, but recommended): 2.4.12 Focus Not Obscured (Enhanced), 2.4.13 Focus Appearance, 3.3.9 Accessible Authentication (Enhanced).
Looking ahead: WCAG 3.0 (W3C Accessibility Guidelines) is in Working Draft (March 2026). It replaces pass/fail with Bronze/Silver/Gold conformance and "Outcomes" instead of "Success Criteria." It is NOT yet a standard — continue targeting WCAG 2.2 AA.
Legal Enforcement Context (2026)
- European Accessibility Act (EAA): Enforced since June 2025 across all 27 EU member states. Applies to digital products and services. Fines up to EUR 3 million. References EN 301 549 (maps to WCAG 2.1 AA).
- ADA Title II (US): Digital accessibility rule effective April 2026 for state/local governments serving 50,000+ people (April 2027 for smaller entities). Requires WCAG 2.1 AA.
- Section 508 (US Federal): References WCAG 2.0 AA (refresh to 2.1/2.2 expected).
Target: WCAG 2.2 AA covers all current legal requirements (superset of 2.1 AA and 2.0 AA).
Five Rules of ARIA
- Prefer native HTML — Use
<button>not<div role="button">. Native elements have built-in keyboard, focus, and semantics. - Don't change native semantics — Don't add
role="heading"to a<button>. Use the correct element. - All ARIA controls must be keyboard operable — If
role="button", handle Enter and Space key events. - Don't use
aria-hidden="true"on focusable elements — Hidden from assistive tech but still focusable creates a "ghost" element. - All interactive elements need an accessible name — Via label,
aria-label,aria-labelledby, or visible text content.
Semantic HTML Anti-Patterns (S1-S8)
S1: Missing lang Attribute on <html>
- Severity: CRITICAL
- Detection:
<htmlwithoutlang= - WCAG: 3.1.1 (A)
<!-- BAD -->
<html>
<!-- GOOD -->
<html lang="en">
Next.js: Set in app/layout.tsx. Angular: Set in src/index.html. Vue/Nuxt: Set in app.vue or nuxt.config.
S2: Multiple <h1> Per Page
- Severity: SUGGESTION
- Detection: Multiple
<h1>elements that make the page heading structure unclear - WCAG: Best practice (supports 1.3.1)
Prefer one <h1> per page representing the main topic. Use <h2> for sections. Multiple <h1> elements are not a strict WCAG violation but can confuse screen reader navigation.
S3: Heading Level Gaps
- Severity: IMPORTANT
- Detection:
<h1>followed by<h3>(skipping<h2>) - WCAG: 1.3.1 (A)
Maintain logical nesting: h1 > h2 > h3 > h4. Style headings with CSS, not by choosing a different heading level.
S4: Div Soup — No Landmark Elements
- Severity: IMPORTANT
- Detection: Pages using only
<div>without<nav>,<main>,<header>,<footer> - WCAG: 1.3.1 (A), 2.4.1 (A)
<!-- GOOD -->
<header>...</header>
<nav aria-label="Main">...</nav>
<main>...</main>
<footer>...</footer>
S5: Layout Tables Without role="presentation"
- Severity: IMPORTANT
- Detection:
<table>without<th>,<caption>, orrole="presentation" - WCAG: 1.3.1 (A)
Use CSS Grid/Flexbox for layout. If table must be used for layout, add role="presentation".
S6: Data Tables Without Headers
- Severity: CRITICAL
- Detection:
<table>with data rows but no<th>elements - WCAG: 1.3.1 (A)
<!-- GOOD -->
<table>
<caption>User list</caption>
<thead>
<tr><th scope="col">Name</th><th scope="col">Email</th></tr>
</thead>
<tbody>
<tr><td>Alice</td><td>alice@example.com</td></tr>
</tbody>
</table>
S7: Non-Descriptive Link Text
- Severity: IMPORTANT
- Detection:
>click here<|>read more<|>learn more<|>here<|>more<|>link< - WCAG: 2.4.4 (A)
<!-- BAD -->
<a href="/pricing">Click here</a>
<!-- GOOD -->
<a href="/pricing">View pricing plans</a>
S8: Interactive Elements Without Semantic HTML
- Severity: CRITICAL
- Detection:
<div.*(?:onClick|@click|\(click\)) - WCAG: 4.1.2 (A)
// BAD — not focusable, no role, no keyboard support
<div onClick={handleClick}>Submit</div>
// GOOD
<button onClick={handleClick}>Submit</button>
ARIA Anti-Patterns (A1-A8)
A1: Redundant ARIA on Native Elements
- Severity: SUGGESTION
- Detection:
<button.*role="button"|<nav.*role="navigation"|<a.*role="link" - WCAG: ARIA Rule 2
Remove redundant ARIA. <button> already has role="button".
A2: aria-hidden="true" on Focusable Element
- Severity: CRITICAL
- Detection:
aria-hidden="true"on focusable elements (button, input, a, [tabindex]) - WCAG: ARIA Rule 4
Use inert attribute or remove from tab order entirely.
A3: Missing Required ARIA Properties
- Severity: CRITICAL
- Detection:
role="tab"withoutaria-selected,role="checkbox"withoutaria-checked - WCAG: 4.1.2 (A)
Required per role: tab needs aria-selected/aria-controls; combobox needs aria-expanded/aria-controls; slider needs aria-valuemin/aria-valuemax/aria-valuenow; checkbox needs aria-checked.
A4: Invalid ARIA Role Values
- Severity: CRITICAL
- Detection:
role="[^"]*"with non-existent values - WCAG: 4.1.2 (A)
Invalid roles are ignored by assistive technology. Common mistakes: role="input", role="text", misspellings.
A5: ARIA Where Native HTML Works
- Severity: IMPORTANT
- Detection:
role="button"on<div>,role="checkbox"on<div> - WCAG: ARIA Rule 1
<!-- BAD — requires manual keyboard, focus, and state management -->
<div role="checkbox" aria-checked="false" tabindex="0">Accept terms</div>
<!-- GOOD — all behavior built-in -->
<label><input type="checkbox" /> Accept terms</label>
A6: Missing aria-label on Icon-Only Buttons
- Severity: CRITICAL
- Detection:
<buttonwith SVG/icon child and no text oraria-label - WCAG: 4.1.2 (A)
<!-- GOOD -->
<button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
A7: role="presentation" on Focusable Elements
- Severity: IMPORTANT
- Detection:
role="presentation"on interactive elements - WCAG: ARIA Rule 4
A8: Missing Live Region for Dynamic Content
- Severity: IMPORTANT
- Detection: Toast/notification components without
role="alert",role="status", oraria-live - WCAG: 4.1.3 (AA)
<!-- GOOD — content announced when injected -->
<div role="status" aria-live="polite">Item saved successfully</div>
<!-- Use role="alert" (assertive) for errors -->
<div role="alert">Failed to save. Please try again.</div>
Keyboard and Focus Anti-Patterns (K1-K7)
K1: onClick Without onKeyDown on Non-Native Elements
- Severity: CRITICAL
- Detection:
(?:onClick|@click|\(click\))on<div>or<span>without keyboard handler - WCAG: 2.1.1 (A)
Use <button> instead. If div is required: add role="button", tabIndex={0}, and handle Enter/Space.
K2: Positive tabindex Values
- Severity: CRITICAL
- Detection:
(?:tabindex="[1-9]\d*"|tabIndex=\{[1-9]\d*\}) - WCAG: 2.4.3 (A)
Only use tabindex="0" (add to tab order) and tabindex="-1" (programmatic focus only).
K3: Focus Trap Without Escape
- Severity: CRITICAL
- Detection: Modal/overlay without Escape key handler or focus trapping
- WCAG: 2.1.2 (A)
Use native <dialog> with showModal() — it provides focus trapping, Escape-to-close, and focus return automatically. Use inert attribute on background content to prevent interaction outside the dialog (96%+ browser support). If custom implementation is needed: trap Tab within the dialog, close on Escape, return focus to the trigger element on close.
K4: Missing Skip Link
- Severity: IMPORTANT
- Detection: No skip link as first focusable element
- WCAG: 2.4.1 (A)
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>...</nav>
<main id="main-content" tabindex="-1">...</main>
.skip-link { position: absolute; top: -40px; left: 0; padding: 8px 16px; background: #000; color: #fff; z-index: 100; }
.skip-link:focus { top: 0; }
K5: outline: none Without Replacement
- Severity: CRITICAL
- Detection:
outline:\s*none|outline:\s*0\bwithout:focus-visiblereplacement - WCAG: 2.4.7 (AA)
/* GOOD */
button:focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }
K6: Mouse-Only Interactions
- Severity: IMPORTANT
- Detection:
onMouseOver|onMouseEnter|@mouseenterwithout keyboard equivalent - WCAG: 2.1.1 (A)
Pair hover with focus events. Use onFocus/onBlur alongside onMouseEnter/onMouseLeave.
K7: Focus Not Returned After Modal Close
- Severity: IMPORTANT
- Detection: Dialog close without restoring focus to trigger
- WCAG: 2.4.3 (A)
Store reference to trigger element. On modal close, call triggerElement.focus().
Form Anti-Patterns (F1-F6)
F1: Input Without Associated Label
- Severity: CRITICAL
- Detection:
<input|<select|<textareawithout<label>,aria-label, oraria-labelledby - WCAG: 1.3.1 (A), 3.3.2 (A)
<!-- GOOD -->
<label for="email">Email address</label>
<input id="email" type="email" placeholder="you@example.com" />
F2: Error Messages Not Linked to Input
- Severity: CRITICAL
- Detection: Error elements near inputs without
aria-describedby - WCAG: 3.3.1 (A)
<input id="email" type="email" aria-describedby="email-error" aria-invalid="true" />
<span id="email-error" class="error">Invalid email format</span>
F3: Required Field Indicated Only by Color or *
- Severity: IMPORTANT
- Detection:
requiredor*withoutaria-required="true"or HTMLrequired - WCAG: 3.3.2 (A), 1.4.1 (A)
<label for="name">Name <span aria-hidden="true">*</span></label>
<input id="name" type="text" required />
<p class="form-note">Fields marked * are required</p>
F4: No Error Summary or Focus on First Error
- Severity: IMPORTANT
- Detection: Form submit handler without focus management on validation failure
- WCAG: 3.3.1 (A)
On submit failure, focus the first invalid field or show and focus an error summary.
F5: CAPTCHA Without Accessible Alternative
- Severity: IMPORTANT
- Detection: Puzzle/image CAPTCHAs without fallback; password fields with
autocomplete='off'or paste-blocking JavaScript - WCAG: 3.3.8 (AA)
Use reCAPTCHA v3 (invisible), hCaptcha accessibility mode, or alternative authentication. Never block paste or autofill on password fields — this violates WCAG 3.3.8.
F6: Placeholder as Label
- Severity: IMPORTANT
- Detection:
placeholder=without accompanying<label>,aria-label, oraria-labelledby - WCAG: 3.3.2 (A)
Always pair placeholder with a visible <label>. Placeholder is a hint, not a label.
Visual and Color Anti-Patterns (V1-V5)
V1: Insufficient Text Contrast
- Severity: CRITICAL
- Detection: Text color combinations below 4.5:1 (normal) or 3:1 (large)
- WCAG: 1.4.3 (AA)
/* BAD — #999 on #fff is ~2.5:1 */
.text { color: #999; background: #fff; }
/* GOOD — #595959 on #fff is 7.0:1 */
.text { color: #595959; background: #fff; }
V2: Information Conveyed by Color Alone
- Severity: CRITICAL
- Detection: Error/success states distinguished only by color
- WCAG: 1.4.1 (A)
Add a secondary indicator: icon, text, pattern, underline, or border.
V3: Fixed Font Sizes Preventing Resize
- Severity: IMPORTANT
- Detection:
font-size:\s*[0-9]*px(excluding root/base) - WCAG: 1.4.4 (AA)
Use rem or em for font sizes. Base font can be px, but content fonts should be relative.
V4: Content Not Reflowing at 320px
- Severity: IMPORTANT
- Detection: Fixed-width containers, horizontal scroll at narrow widths
- WCAG: 1.4.10 (AA)
Use responsive layouts (Grid, Flexbox). Test at 320px CSS width. Avoid fixed-width containers.
V5: Animation Without prefers-reduced-motion
- Severity: SUGGESTION
- Detection:
animation:|transition:withoutprefers-reduced-motionmedia query - WCAG: 2.3.3 (AAA)
Best practice and AAA enhancement. Gate non-essential animations behind prefers-reduced-motion so users who request less motion are not forced to experience interaction-triggered effects.
@media (prefers-reduced-motion: no-preference) {
.card { transition: transform 0.3s ease; }
.card:hover { transform: scale(1.05); }
}
Media Anti-Patterns (D1-D4)
D1: Informational Image Without Alt Text
- Severity: CRITICAL
- Detection:
<img|<Imagewithoutalt=attribute - WCAG: 1.1.1 (A)
Alt text decision tree: decorative = alt=""; contains text = include that text; functional = describe action; informational = describe content.
D2: Decorative Image with Non-Empty Alt
- Severity: SUGGESTION
- Detection: Decorative images with meaningful alt text
- WCAG: 1.1.1 (A)
Use alt="" for decorative images. Add aria-hidden="true" for decorative SVGs.
D3: Video Without Captions
- Severity: CRITICAL
- Detection:
<videowithout<track kind="captions"> - WCAG: 1.2.2 (A)
<video src="/tutorial.mp4" controls>
<track kind="captions" src="/tutorial-en.vtt" srclang="en" label="English" default />
</video>
D4: Audio/Video Autoplay
- Severity: IMPORTANT
- Detection:
autoplayattribute withoutmuted - WCAG: 1.4.2 (A)
Never autoplay audio. If video autoplays, start muted with controls.
Framework-Specific: React / Next.js (RX1-RX4)
RX1: Missing htmlFor on <label>
- Severity: IMPORTANT
- Detection:
<label.*for="in JSX (should behtmlFor) - WCAG: 1.3.1 (A), 3.3.2 (A)
RX2: SPA Route Change Without Focus Management
- Severity: IMPORTANT
- Detection: Navigation without focus management or live region
- WCAG: 4.1.3 (AA)
After route change, focus the main heading or announce the new page title via a live region. Next.js includes a built-in route announcer (since v13) that reads document.title, then <h1>, then pathname. Ensure every page has a unique <title>.
RX3: Fragment Root Causing Focus Loss on Re-render
- Severity: SUGGESTION
- Detection:
<>...</>root with conditional rendering causing DOM restructuring - WCAG: 2.4.3 (A)
Use key prop to preserve DOM identity, or manually restore focus with useRef + useEffect.
RX4: Injected HTML Without ARIA Consideration
- Severity: IMPORTANT
- Detection: Rich text rendering without accessibility validation
- WCAG: 1.3.1 (A)
Sanitize and validate injected HTML for heading hierarchy, alt text, and ARIA structure.
Framework-Specific: Angular (NG1-NG4)
NG1: (click) on <div> Without Role and Keyboard Support
- Severity: CRITICAL
- Detection:
(click)on<div>or<span>withoutrole=,tabindex,(keydown) - WCAG: 2.1.1 (A), 4.1.2 (A)
Use <button>. If div required: add role="button", tabindex="0", (keydown.enter), (keydown.space).
NG2: Missing cdkTrapFocus in Modal Components
- Severity: IMPORTANT
- Detection: Modal components without
cdkTrapFocus - WCAG: 2.1.2 (A)
<div class="modal" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">...</div>
Angular CDK's Dialog service handles focus trapping and restoration automatically.
NG3: Route Change Without LiveAnnouncer
- Severity: IMPORTANT
- Detection: Angular Router navigation without
LiveAnnouncer - WCAG: 4.1.3 (AA)
router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => {
liveAnnouncer.announce(titleService.getTitle(), 'polite');
});
NG4: Template-Driven Forms Without Accessible Validation
- Severity: IMPORTANT
- Detection: Forms showing errors without
[attr.aria-invalid]or[attr.aria-describedby] - WCAG: 3.3.1 (A), 3.3.3 (AA)
Bind [attr.aria-invalid] and [attr.aria-describedby] to form control state.
Framework-Specific: Vue (VU1-VU3)
VU1: @click on Non-Interactive Element Without Role and Keyboard
- Severity: CRITICAL
- Detection:
@clickon<div>or<span>withoutrole=,tabindex,@keydown - WCAG: 2.1.1 (A), 4.1.2 (A)
Use <button>. Or add role="button", tabindex="0", @keydown.enter, @keydown.space.prevent.
VU2: v-if Toggle Without Focus Management
- Severity: IMPORTANT
- Detection:
v-iftoggling without managing focus vianextTick - WCAG: 2.4.3 (A)
<script setup>
import { ref, watch, nextTick } from 'vue';
const showPanel = ref(false);
const panel = ref(null);
watch(showPanel, async (val) => {
if (val) { await nextTick(); panel.value?.focus(); }
});
</script>
VU3: v-html Injecting Content Without Accessible Structure
- Severity: IMPORTANT
- Detection:
v-htmlrendering user or CMS content - WCAG: 1.3.1 (A)
Sanitize and validate HTML for heading hierarchy, alt text, and ARIA structure before injection.
Keyboard Interaction Reference
| Key | Expected Behavior |
|---|---|
Tab |
Move focus to next focusable element in DOM order |
Shift+Tab |
Move focus to previous focusable element |
Enter |
Activate buttons and links |
Space |
Activate buttons, toggle checkboxes, select radio buttons |
Escape |
Close modals, dialogs, popovers, dropdowns |
Arrow Up/Down |
Navigate within menus, listboxes, radio groups, tabs |
Arrow Left/Right |
Navigate within tab bars, sliders, radio groups |
Home |
Move to first item in list, menu, or tab bar |
End |
Move to last item in list, menu, or tab bar |
Widget-Specific Patterns
| Widget | Tab enters | Internal nav | Activate | Exit |
|---|---|---|---|---|
| Tab bar | Focus active tab | Arrow Left/Right | automatic or Enter | Tab out |
| Menu | Focus first item | Arrow Up/Down | Enter | Escape |
| Dialog | Focus first element | Tab cycles within | Enter on buttons | Escape |
| Combobox | Focus input | Arrow Up/Down | Enter selects | Escape closes |
| Tree view | Focus first node | Arrow keys | Enter/Space | Tab out |
Color Contrast Quick Reference
Text Contrast (WCAG 1.4.3 AA)
| Text Type | Minimum Ratio |
|---|---|
| Normal text (< 18pt / < 14pt bold) | 4.5:1 |
| Large text (>= 18pt / >= 14pt bold) | 3:1 |
| Incidental (disabled, decorative) | No requirement |
Non-Text Contrast (WCAG 1.4.11 AA)
| Element | Minimum Ratio |
|---|---|
| UI components (borders, icons) | 3:1 against adjacent |
| Graphical objects | 3:1 against adjacent |
| Focus indicators | 3:1 against background |
Accessibility Checklist (POUR)
Perceivable
- All images have appropriate alt text (descriptive or empty for decorative)
- Videos have synchronized captions
- Page uses semantic landmarks:
<header>,<nav>,<main>,<footer> - Headings follow logical hierarchy (h1 > h2 > h3, no gaps)
- Text contrast meets 4.5:1 (normal) / 3:1 (large)
- UI component contrast meets 3:1
- Information not conveyed by color alone
- Content reflows at 320px without horizontal scroll
<html lang="...">is set correctly- Text resizable to 200% without loss of content
Operable
- All functionality accessible via keyboard
- No keyboard traps (Escape closes overlays)
- Skip link provided as first focusable element
- Focus indicator visible on all interactive elements
- Focus order matches visual order
- Focus not obscured by sticky headers/footers
- Focus returned to trigger after modal close
- Touch targets at least 24x24 CSS px
- Animations respect
prefers-reduced-motion - No content flashes more than 3 times per second
Understandable
- All form inputs have associated
<label>oraria-label - Error messages linked to inputs via
aria-describedby - Required fields indicated with
requiredoraria-required - Error summary or focus-on-first-error on submit failure
- No unexpected context changes on focus or input
Robust
- All interactive elements have accessible name, role, and state
- ARIA roles have required properties
- No
aria-hidden="true"on focusable elements - Dynamic content announced via live regions
- SPA route changes announced to screen readers
- No redundant ARIA on native HTML elements