BXUP Track progress & test yourself — free
Frontend & Mobile · Playbook · Core · ~5 min read
FRONTEND & MOBILE · PLAYBOOK · CORE

Accessibility Constraints and CI Gates for AI-Generated UI

Generators produce div-buttons, unlabeled inputs, and focus-order chaos by default — not because models cannot write accessible markup, but because nothing forced them to. This playbook installs the force: accessibility constraints in every instruction file, and an axe-core CI gate that AI-authored PRs cannot skip.

Tier: Core · Workflow area: Accessibility

Prevention beats audit. Where the three-layer audit (pb-29) finds issues after the fact, this playbook stops the automatable classes from being generated at all, and blocks the ones that slip through before merge.

When to use

  • Your team generates a meaningful share of its UI and accessibility findings keep recurring across PRs.
  • You just finished an audit and want the fixed classes to stay fixed.
  • You are setting up Copilot instructions, Cursor rules, or a CLAUDE.md for a frontend repo — a11y constraints belong in the first version.
  • An incoming compliance requirement (EAA, Section 508) means "we'll fix it later" is no longer a plan.

Skip the instruction-file half if your design system's primitives already enforce semantics and your generators are configured to use only those primitives — then the CI gate alone may suffice.

Prerequisites

  • An instruction-file mechanism already in place: .github/copilot-instructions.md, .cursor/rules/, or CLAUDE.md at repo root.
  • jest-axe (or vitest-axe) runnable in your component test setup, and @axe-core/playwright or axe-core CLI runnable against a built preview for page-level scans.
  • A design system whose primitives are accessible — gating on axe while generating from broken primitives just documents your primitives' bugs repeatedly.
  • CI with required checks and branch protection, so "cannot be skipped" is enforced by the platform, not by convention.

The workflow

  1. Write the constraint block from your audit findings, not from a generic list. Take the recurring defect classes from your reviews and audits and turn each into an imperative rule with a correct example. A strong starting block:

    ## Accessibility (non-negotiable)
    - Interactive elements are native elements: <button>, <a href>, <input>,
      <select>. Never a div/span with onClick. If a design needs a styled
      control, use our primitives (Button, Link, IconButton).
    - Every input gets a programmatically associated label. Placeholder
      is never the label.
    - Icon-only controls get an accessible name (aria-label or visually
      hidden text). Use our <VisuallyHidden> helper.
    - DOM order matches visual order. Never reorder with CSS alone
      (order, flex-direction: *-reverse) for content that has a reading order.
    - Images: meaningful ones get descriptive alt; decorative ones get alt="".
    - Custom widgets (menu, dialog, tabs, combobox) follow the ARIA APG
      pattern. If unsure, use our primitive or stop and ask.
    - Every new component's tests include a jest-axe assertion.
    

    Keep it under ~30 lines. Constraint blocks that scroll get truncated out of context windows and out of attention.

  2. Add few-shot exemplars. Instructions state rules; exemplars make generators imitate them. Reference two or three of your most accessible components explicitly: "Follow the labeling and focus-management approach in components/forms/TextField.tsx and components/overlay/Dialog.tsx." Generators copy what they are shown far more reliably than what they are told.

  3. Install the component-level gate. Add jest-axe to the shared test setup so every rendered-component test can assert cleanliness:

    import { axe } from "jest-axe";
    
    it("has no detectable a11y violations", async () => {
      const { container } = render(<CheckoutForm />);
      expect(await axe(container)).toHaveNoViolations();
    });
    

    Update your test-generation instructions (and pb-22-style prompts) so every generated component test includes this assertion for each meaningful state — not just the default render.

  4. Install the page-level gate. Component scans miss composition problems: duplicate landmarks, heading-order breaks, focus obligations between components. Run @axe-core/playwright against your preview build's key routes in CI, with a zero-new-violations policy — baseline existing debt in a tracked allowlist file, and fail the build only on regressions. Shrink the baseline on a schedule.

  5. Make the gate unskippable and label the escape hatch. Mark both scans as required checks in branch protection. Allow rule-level exceptions only via an inline // axe-ignore: <rule> -- <reason> <ticket> convention that a CI script counts and reports weekly. An invisible exception is a silent regression channel.

Key Takeaway:

Constraints shape what generators emit; gates catch what constraints miss; branch protection makes the gate real. All three together turn accessibility from a recurring review finding into a property of the pipeline.

Tool variants

  • GitHub Copilot:.github/copilot-instructions.md for repo-wide rules; path-scoped *.instructions.md files to give component directories stricter rules than scripts.
  • Cursor:.cursor/rules/accessibility.mdc with alwaysApply for the constraint block; attach exemplar files via the rule's referenced-files mechanism.
  • Claude Code: the block lives in CLAUDE.md; for agentic runs, also add a check-before-done instruction — "run npm run test:a11y and fix violations before reporting completion."
  • axe-core family: jest-axe/vitest-axe at component level, @axe-core/playwright or Lighthouse CI a11y category at page level.

Verification

  • Generate a deliberately underspecified component ("build a card with a dismiss icon and an image") with the constraints in place — output uses native elements, labeled controls, and correct alt handling without being asked.
  • CI fails on a seeded violation: push a branch with a div-button and confirm both the component and page gates go red and block merge.
  • Branch protection lists both a11y checks as required; a force-merge attempt without them is refused.
  • The exception count report exists and trends flat or down.

Pitfalls

  • Generic constraint dumps. Pasting all of WCAG into an instruction file buries the ten rules that matter for your defect history. Derive rules from your own findings and keep the block short.
  • Gate without constraints. axe-core catches maybe a third of issues; if generation is unconstrained, the untestable two-thirds (meaningful names, reading order, APG conformance) ship unchecked. You need both halves.
  • Zero-violations-ever policies on legacy code. Failing the build on pre-existing debt makes teams disable the gate within a week. Baseline, block regressions, burn down the baseline deliberately.
  • jest-axe on the default state only. Violations live in error states, open modals, and expanded menus. Scan each meaningful state the test renders.
  • Assuming the gate proves accessibility. Passing axe means the automatable subset is clean. Keyboard behavior, announcement quality, and focus management still need the audit and repair playbooks (pb-29, pb-32).

Restricted-environment variant

Everything in this playbook runs locally or in your CI: axe-core, jest-axe, Playwright, instruction files. Nothing ships data to a model at gate time.

  • In-tenant generators: the same constraint blocks work verbatim in Copilot Business or Bedrock/Azure-backed assistants — instruction files are just repo files.
  • Local models: smaller models follow short imperative constraint lists better than long prose; keep the block to the ten hardest rules and lean more heavily on the CI gate to catch the rest.
Warning

Accessible primitives are the load-bearing wall. If your Button or Dialog primitive is itself broken, constraints will faithfully reproduce the breakage at scale and the gate will flag it in every PR. Audit and fix primitives first — one fix there removes a finding class everywhere.

Success criteria

  • New AI-generated PRs arrive with zero automatable a11y violations — the gate goes weeks without firing on generated code.
  • The baseline allowlist shrinks release over release until it is empty.
  • Audit findings (pb-29) shift toward the genuinely non-automatable classes — the pipeline handles the rest.
  • Instruction-file a11y rules are updated within a sprint whenever review or audit surfaces a new recurring class.
This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in