

<!--
The Beginner's Accessibility File for Claude Code
Pounce Agency, v1.2, 21 May 2026 (Global Accessibility Awareness Day)
Maintained: https://gaad.pounceagency.com.au
v1.2 reviewer: [Sean Gardner]
-->

# Accessibility instructions

You are generating production code that will be used by people with disabilities on the same terms as people without. The person prompting you cannot evaluate accessibility output and will not catch failures themselves. Apply the rules below to every piece of code you write, on the first pass, without being asked. If a user request would violate a rule, follow the rule and state the reason in one sentence.

These rules are non-negotiable defaults. Do not relax them for visual polish, brevity, or implementation speed.

## Structure

1. Use the HTML element that matches the job. `<button>` for buttons. `<a>` for links. `<h1>`–`<h6>` for headings. `<nav>`, `<main>`, `<footer>`, `<aside>` for landmarks. Never style a `<div>` or `<span>` as an interactive element.

2. Use heading levels sequentially. After `<h1>`, the next level is `<h2>`. After `<h2>`, the next is `<h2>` or `<h3>`. Never skip a level. Use one clear `<h1>` per page or route. This includes headings inside footers, sidebars, and card grids — audit the full heading tree of the page, not just the section you are editing.

3. Prefer semantic HTML to ARIA. If a native element does the job (`<button>`, `<details>`, `<dialog>`, `<nav>`), use it. Add ARIA only where no semantic equivalent exists. Specific anti-patterns to avoid: do not use `<div role="list">` / `<div role="listitem">` when `<ul>` / `<li>` or `<ol>` / `<li>` would work. Do not use `<div role="button">` when `<button>` would work. If a grid of cards is a list, use a real list element — CSS Grid and Flexbox work on `<ul>` and `<li>`.

4. For any custom control (toggle, switch, combobox, listbox, tablist, tree, slider, menu, accordion, disclosure), implement the WAI-ARIA Authoring Practices pattern for that widget — role, state, keyboard behaviour, focus management. Do not invent custom keyboard or ARIA patterns.

5. Every page sets `<html lang="...">` to the correct language code, has a unique `<title>` that describes the page, and provides a skip-to-content link as the first focusable element when the page has repeated navigation.

6. Mark language changes inline (`<span lang="fr">…</span>`). Wrap related controls in `<fieldset>` with a `<legend>`. Use `<table>` only for tabular data, with `<th scope="col|row">` and a `<caption>`.

7. Every `<section>` that represents a distinct region of the page must contain a heading element (`<h2>`–`<h6>`) that labels it. If `aria-labelledby` is used on a `<section>`, the target must be a heading element, not a `<p>` or `<span>` — a `<p>` gives the section an accessible name but leaves a gap in the heading outline for users navigating by heading level.

## Keyboard

8. Everything a mouse can do, the keyboard must do. Every interactive element is reachable with Tab and operable with Enter, Space, Escape, or arrow keys as the relevant pattern requires.

9. Tab order matches visual reading order. Never use positive `tabindex` values to manipulate order; restructure the DOM instead.

10. Manage focus on state changes. When an overlay opens, move focus into it. When it closes, return focus to the element that opened it. On route changes or client-side redirects (including `window.location.href` assignments after form submission), move focus to the new page's `<main>` region or `<h1>`, or announce the transition via `aria-live` before navigating.

11. Focus indicators must be visible against every background the element can sit on, in every state. Never set `outline: none` or `outline: 0` without a replacement at least as visible.

12. Never make functionality available on hover alone. If hover reveals content (tooltip, menu, disclosure), the same content must be reachable on keyboard focus, persist while focused, and dismiss on Escape. Triggers for hover-revealed content must themselves be focusable.

13. Any single-character keyboard shortcut can be turned off, remapped, or is only active while a relevant control has focus.

## Content

14. Link text describes the destination; button text describes the action. Avoid "click here," "read more," "learn more." Icon-only controls require an `aria-label`. If a link wraps only an image, the image `alt` text must describe the link destination (e.g., `alt="Pounce Agency homepage"`), and consider adding `aria-label` to the link as well for clarity.

15. Every image has an `alt` attribute. Informative images describe the information conveyed. Decorative images use `alt=""`. Functional images (an icon used as a button) describe the action.

16. Never describe controls by sensory features alone ("the green button," "the box on the right," "the icon below"). Pair sensory language with the control's name or function.

17. Expand abbreviations on first use or wrap them in `<abbr title="…">`. Write to the reading level appropriate to the audience; avoid jargon where plain language would serve.

18. Never ship placeholder links (`href="#"`) or dead-end interactions. Every link must resolve to a real destination or be removed. If a section is not yet built, remove the link entirely or replace it with non-interactive text — a `href="#"` link that goes nowhere confuses keyboard and screen reader users and violates WCAG 2.4.4.

## Forms

19. Every form input has a programmatic label. Use `<label for="id">` matched to the input's `id`, or wrap the input inside the label. Placeholder text is not a label.

20. Use the correct input type for the data (`type="email"`, `tel`, `url`, `number`, `date`, `search`). Use `autocomplete` tokens on identity, contact, address, payment, and credential fields so browsers and assistive tech can populate them.

21. Mark required fields with text or an icon alongside the label, never with colour or asterisk alone. Use the `required` attribute and announce required status to assistive tech.

22. Form validation errors appear next to the failing field, set `aria-invalid="true"` on the input, and link via `aria-describedby` to the error message. Error containers that appear dynamically (e.g., shown via JS on submit) must use `role="alert"` or `aria-live="assertive"` so screen readers announce them when they appear — `aria-describedby` alone is only read when the input receives focus, which is not enough if the user is not already focused on the field. Errors are not communicated by colour alone.

23. Error messages explain what went wrong and how to fix it ("Email must include an @ symbol"), not just that something is wrong ("Invalid input").

24. For submissions that create legal or financial commitments, give the user a way to review, confirm, and correct their input before final submission.

## Visual and layout

25. Body text contrast ratio is at least 4.5:1 against its background. Large text (18pt+ or 14pt+ bold) and UI components are at least 3:1. Apply contrast checks across every state and background the element can sit on. Use existing design tokens; do not invent new colours mid-build. **Critical: do not use `rgba()` or `opacity` to reduce text visibility unless the text sits on a flat, solid background colour.** When text sits over a gradient, video, image, or semi-transparent container, the effective contrast shifts with the content behind it and cannot be reliably verified. In those cases, either place a solid opaque background behind the text container, or use a fully opaque text colour that passes 4.5:1 against the worst-case background.

26. Colour is never the only signal. Pair colour with text, icon, or shape for every status, validation state, required-field indicator, and data-visualisation distinction.

27. Touch targets are at least 44×44 CSS pixels. This applies to all viewports, not just mobile. Targets that appear smaller visually must use padding, min-height, or min-width to reach the minimum clickable/tappable area. Adjacent targets have spacing between them. Common violations: navigation links and footer links that are styled as inline text with no vertical padding.

28. Use relative units (`rem`, `em`, `%`, `ch`) for typography and spacing. Content must reflow without horizontal scroll at a 320 CSS pixel viewport. Do not set fixed widths that prevent reflow.

29. Text resize to 200% must not cause loss of content or function. Text-spacing overrides (line-height 1.5×, paragraph spacing 2×, letter-spacing 0.12×, word-spacing 0.16×) must not break the layout.

30. Never set `user-scalable=no` or `maximum-scale=1` on the viewport meta tag. Never lock orientation to portrait or landscape unless the experience genuinely requires it (e.g., piano roll).

## Motion and media

31. Honour `prefers-reduced-motion`. Disable non-essential animation, parallax, and transitions longer than 200ms when the preference is set. Wrap motion in `@media (prefers-reduced-motion: no-preference)` where appropriate.

32. Nothing flashes more than three times per second. Anything that auto-animates, auto-scrolls, or plays audio for longer than 5 seconds must have a pause, stop, or mute control. **These controls must exist in the HTML markup, not only injected by JavaScript** — if JS fails to load or execute, the user must still have a way to stop the motion. For `<video autoplay>`, either include a `<button>` in the markup adjacent to the video, or use `prefers-reduced-motion` in CSS to prevent autoplay entirely when motion is reduced and provide a manual play trigger.

33. Video has synchronised captions for prerecorded content and live content. Audio-only content has a transcript. Do not autoplay audio without an obvious way to stop it. Decorative background videos with no speech content should include a comment or `<track kind="descriptions">` element that makes the decorative intent explicit.

## Pointer and touch

34. Functions performed by a multi-point gesture (pinch, two-finger swipe) or a path-based gesture (drawing a shape) must also be operable with a single pointer without a path.

35. Drag-and-drop interactions must have a keyboard alternative (cut/paste, move-up/move-down buttons, reorder dropdowns) that achieves the same outcome.

36. Activate functions on pointer-up, not pointer-down, so the user can drag away to cancel. Exceptions: keyboard emulation (pointer-down is fine for instant feedback) and where down-activation is essential (e.g. piano keys).

## Overlays

(Modals, dialogs, drawers, popovers, sheets, menus, tooltips, command palettes.)

37. Use a tested overlay pattern or library appropriate to the stack. Do not build focus management, ARIA, or dismiss behaviour from scratch. Modal overlays trap focus until closed; non-modal overlays (popovers, menus, tooltips) close on Escape and outside interaction but do not trap. **Mobile navigation overlays are modal overlays.** They must trap focus: Tab from the last focusable element wraps to the first, and Shift+Tab from the first wraps to the last. Without a focus trap, keyboard users can Tab behind the overlay into invisible content.

38. Every overlay has an accessible name (`aria-label` or `aria-labelledby`), dismisses on Escape, returns focus to the trigger on close, and is announced to assistive technology via the library's mechanism.

## Feedback and time

39. Status changes — saving, loading, results updated, filter applied, validation success — are announced via `role="status"` or `aria-live="polite"`. Errors needing immediate attention use `role="alert"` or `aria-live="assertive"`, sparingly. When a status region (e.g., a preloader with `role="status"`) is no longer needed, remove it from the DOM entirely or set `role="none"` — do not leave stale `role="status"` containers in the page, even if visually hidden, as screen readers may continue to monitor them.

40. Disabled elements use the `disabled` attribute on form controls or `aria-disabled="true"` on custom widgets. The disabled state is distinguishable by more than colour — reduced opacity, cursor change, and text adjustment together.

41. Session timeouts warn the user before expiry and offer a way to extend without losing data. Time limits on tasks can be turned off, adjusted, or extended unless the time limit is essential (e.g., real-time auctions).

42. Navigation, labelling, and component behaviour stay consistent across pages of the same site. The same icon means the same thing everywhere; the same control sits in the same place.

## Responsive design

43. When hiding navigation or any interactive element at a breakpoint (e.g., `display: none` on a `<nav>` at 768px), you must provide an alternative that preserves the same functionality at that viewport size. A hamburger menu button, a disclosure widget, or a bottom sheet are all acceptable — but removing navigation entirely with no replacement is a WCAG 2.1.1 (Keyboard) and 3.2.3 (Consistent Navigation) failure. The replacement must be keyboard-operable and visible to assistive technology.

44. Test the logical consequences of every `display: none` and `visibility: hidden` in responsive media queries. Both remove elements from the accessibility tree. If the hidden element was the only way to reach a function, that function is now inaccessible at that breakpoint.

## Markup validation

45. Every HTML tag you open, you must close. Before completing a section of markup, verify that all `<span>`, `<div>`, `<a>`, and other elements are properly closed and not accidentally nested. Malformed markup (e.g., a `<span>` that opens inside an `<h2>` but is not closed before a sibling `<span>` opens) will be auto-repaired by the browser in unpredictable ways, creating a DOM tree that differs from your intent. Screen readers parse the DOM, not the source — if the DOM is wrong, the reading order is wrong.

46. After generating or editing a complete HTML document, mentally walk the tag structure of any complex element (headings with nested spans, lists with nested links, forms with grouped inputs) and confirm each opening tag has a corresponding closing tag at the correct nesting depth.

## No-JS resilience

47. Content must never be invisible without JavaScript. If CSS sets initial styles for animation (e.g., `opacity: 0` on elements that JS will animate in), gate those styles behind a JS-added class on `<html>` or `<body>` (e.g., `.js-ready .gsap-reveal { opacity: 0 }`). Without the class, all content is visible by default. Pair with a `prefers-reduced-motion: reduce` override that forces `opacity: 1` and `transform: none`.

48. Final data values must be present in the DOM **and** in accessible attributes regardless of animation state. If a number counts up visually from 0 to 96: (a) the accessible name (`aria-label`) or a `<span class="sr-only">` must contain the real value before animation runs, (b) the visible text content in the HTML source must also be the final value (e.g., `96`, not `0`), so the number is correct without JavaScript, and (c) the animated visual element must be marked `aria-hidden="true"` so screen readers read the stable value, not the changing one. JavaScript may reset the visible number to `0` before animating, but the DOM default must be the real value.

## Text colour tokens

49. Never use `rgba()` or `opacity` to set text colour. Semi-transparent text on a solid background produces a mathematically verifiable contrast ratio — but the same text over a gradient, video, image, or semi-transparent container shifts unpredictably. Use solid hex or HSL colour tokens for all text. Define muted/secondary text as a named token (e.g., `--text-muted: #B3B3B3`) and use that token everywhere. This eliminates an entire class of contrast-audit ambiguity. **When defining or changing a muted colour token, compute the contrast ratio against the darkest background it will appear on and confirm it meets 4.5:1 for normal text.** Common safe values on `#000`: `#B3B3B3` = 7.5:1, `#8C8C8C` = 5.3:1, `#999999` = 5.9:1. Do not use `#808080` (4.0:1 — fails AA for normal text).

## Form error announcements

50. Form error containers must use `aria-live="assertive"` (not `role="alert"` on elements toggled with `display: none`). Start error containers empty in the DOM; inject the error message text via JavaScript when validation fails. This ensures the `aria-live` region is in the accessibility tree before the content change, so screen readers reliably announce the new text. Clearing the text content (not hiding the container) is the correct way to "dismiss" the error — it keeps the live region in the tree for future announcements.

## Performance

51. Below-fold sections should use `content-visibility: auto` with `contain-intrinsic-size` to defer rendering until the user scrolls near them. This significantly reduces initial paint time on long pages.

52. Videos below the fold must use `preload="none"` so the browser does not fetch video data until the section is near the viewport. Hero/above-fold videos may use `preload="auto"` or omit the attribute.

53. Only load the font weights actually used in the stylesheet. Unused weights add network requests and delay text rendering. Audit `font-weight` values in CSS before adding weights to the Google Fonts URL.

54. Use `will-change` sparingly and only on elements that are continuously animated (e.g., a WebGL canvas, a CSS animation that runs indefinitely). Do not apply it to elements that animate once on scroll — the compositing cost outweighs the benefit.

## Pre-completion verification checklist

**Before reporting any task as complete, audit the full page against this checklist.** Do not skip this step. The rules above govern what to build; this checklist governs what to verify. Most accessibility failures that survive a rules-aware build are caught by a whole-page audit, not by re-reading the rule that was already in place.

- [ ] **Heading tree**: List every `<h1>`–`<h6>` on the page in order. Confirm no levels are skipped (h2 → h4 is a skip). Confirm every `<section>` with content has a heading.
- [ ] **Landmark completeness**: Confirm `<main>`, `<header>`, `<nav>`, and `<footer>` are present. Confirm `<nav>` is reachable at every viewport width (including mobile breakpoints).
- [ ] **Tag closure**: For any heading, list, or interactive element with nested tags, confirm every opening tag has a closing tag at the correct depth.
- [ ] **Link audit**: Confirm no `href="#"` placeholder links remain. Every link resolves to a real target.
- [ ] **Touch targets**: Confirm every link, button, and input is at least 44×44 CSS pixels (use padding if the visual element is smaller). Check nav links and footer links specifically.
- [ ] **Contrast on layered backgrounds**: For any text over a gradient, image, video, or semi-transparent container, confirm the text colour is fully opaque and passes 4.5:1 against the darkest plausible background in the stack.
- [ ] **Dynamic ARIA cleanup**: Confirm that `role="status"`, `role="alert"`, and `aria-live` regions are removed or neutralised after their content is no longer relevant.
- [ ] **Form error announcements**: Submit each form empty and confirm error messages are announced (they must use `role="alert"` or `aria-live`, not just `aria-describedby`).
- [ ] **Responsive nav**: Resize to 768px and 480px. Confirm all navigation options are still reachable via keyboard.
- [ ] **No-JS fallback for motion controls**: Confirm that if JavaScript fails, autoplay video/animation either doesn't play or has a non-JS pause mechanism (e.g., `prefers-reduced-motion` preventing autoplay in CSS).
- [ ] **No-JS content visibility**: Disable JavaScript and confirm all text content is visible. No element should be stuck at `opacity: 0` or `display: none` without JS.
- [ ] **Animated data values**: For any number that animates (counters, progress), confirm the real value is in an `aria-label` or `sr-only` span, the HTML source text content is the final value (not `0`), and the animated element is `aria-hidden="true"`.
- [ ] **Text colour tokens**: Confirm no text colour uses `rgba()` or `opacity`. All text colours should be solid hex/HSL tokens. Compute contrast ratios for every muted token against its darkest background — minimum 4.5:1 for normal text.
- [ ] **Form error live regions**: Confirm error containers use `aria-live="assertive"`, start empty, and have text injected on validation failure (not toggled via `display`).
- [ ] **Focus trap on overlays**: Confirm mobile nav and any modal overlay traps focus (Tab wraps at edges). Test with keyboard only — Tab through all focusable elements in the overlay and confirm focus never escapes behind it.
- [ ] **Below-fold video loading**: Confirm videos not visible on initial load use `preload="none"`. Only the hero/above-fold video should load eagerly.
- [ ] **Pause controls in markup**: Confirm every auto-playing element (video, marquee, carousel) has a pause button in the HTML source, not only injected by JavaScript.

---

<!-- The sections below are user-facing context. They sit in the same file so the rules and their limits travel together. -->

## What this file does not do

This file does not produce WCAG 2.2 AA compliance. It does not test rendered output, focus traps that leak, screen-reader announcements that fail to fire, or contrast failures in dynamic states. It is a prompt-layer baseline, not a verification layer.

If the product must meet a legal standard (EU Accessibility Act, Australia's Disability Discrimination Act, US ADA Title III) or pass procurement, the prompting user also needs: automated testing with axe-core or equivalent, manual keyboard testing, screen-reader smoke tests (VoiceOver, NVDA, JAWS), and a review by an accessibility professional.

## When to bring in a professional

For products taking payments, handling health or financial data, serving government, or selling into regulated industries — engage a specialist before launch.

**Australia** — Intopia (https://intopia.digital), Centre For Accessibility Australia (https://www.centreforaccessibility.org.au), Vision Australia Digital Access (https://www.visionaustralia.org/services/digital-access).

**Global** — Deque Systems, WebAIM, the IAAP-certified professional directory.

## Version and feedback

v1.2 — 21 May 2026 (Global Accessibility Awareness Day). 54 rules + pre-completion checklist. Opinionated baseline written against Claude Opus 4.6 and Sonnet 4.6. Reviewed by [v1.2 reviewer — pending]. Maintained at gaad.pounceagency.com.au with public changelog. Send feedback to [contact details].


