Accessibility Bug Reporting Best Practices
This guide explains how to write accessibility bug reports that are clear, reproducible, and actionable. It is optimized for the output of automated testing tools and AI agents, but applies equally to manually discovered issues.
A good accessibility bug report provides enough information for a developer who was not present during testing to reproduce, understand, and fix the problem — without requiring additional back-and-forth.
1. Core Principle
Reproducibility is the single most important quality of a bug report. If a developer cannot reproduce the problem, they cannot fix it. Every field in an accessibility report exists to close the gap between finding a bug and fixing it.
Accessibility bugs have extra requirements compared with general bugs because:
- The failure often only surfaces with a specific assistive technology (AT) or browser combination.
- The violation may be visible in the DOM but invisible to sighted keyboard users, or vice versa.
- Automated tools detect potential violations; human or AT confirmation is often needed to establish real-world impact.
- The fix must not introduce new barriers for other users.
2. Required Fields
Every accessibility bug report must include the following fields.
2.1 Page URL
Provide the exact URL where the issue was found, including query string and fragment identifier if relevant.
URL: https://example.com/checkout?step=2#payment-form
If the issue appears on multiple pages, list all affected URLs or describe the URL pattern (e.g. “all /product/* pages”).
2.2 XPath (Simplest Form)
Provide the shortest XPath that uniquely identifies the failing element on the page. Simple attribute-based selectors are preferred over full absolute paths.
//button[@id="submit-payment"]
//input[@name="card-number"]
//div[@role="dialog"][@aria-labelledby="modal-title"]
If the element has no stable ID or name attribute, add the next-shortest unique selector:
//nav[@aria-label="Main navigation"]//a[contains(text(),"Sign in")]
2.3 XPath with Hierarchy (Full DOM Path)
Provide the complete ancestor chain when the context is ambiguous or when the simplified XPath could match multiple elements.
/html/body/main/section[@id="checkout"]/form[@id="payment-form"]/fieldset[2]/input[@name="card-number"]
Automated tools should emit both forms. The hierarchy path supports debugging in environments where IDs change dynamically (e.g. React, Angular).
2.4 HTML Snippet
Include the minimal HTML fragment that demonstrates the problem. Trim surrounding markup to the smallest context that still shows the issue.
Good — shows only what matters:
<button>
<img src="close.svg">
</button>
Problem: The image has no alt attribute, so the button has no accessible name.
Expected:
<button>
<img src="close.svg" alt="Close dialog">
</button>
Include parent elements only when the violation depends on the relationship between parent and child (e.g. missing <label> for <input>, or a <table> without <th scope>).
2.5 WCAG Success Criterion
Cite the specific WCAG 2.1 or 2.2 Success Criterion violated, including its level (A, AA, or AAA).
WCAG SC: 1.1.1 Non-text Content (Level A)
WCAG SC: 4.1.2 Name, Role, Value (Level A)
WCAG SC: 1.4.3 Contrast (Minimum) (Level AA)
Refer to the WCAG 2.2 Quick Reference for the full list of criteria.
2.6 ACT Rule or Checker Rule
Reference the specific rule from the testing tool or from the W3C Accessibility Conformance Testing (ACT) framework.
Axe-core rule:
Rule ID: image-alt
Rule: Images must have alternate text
Tool: axe-core 4.x
ACT rule:
ACT Rule: 23a2a8 — Image button has accessible name
URL: https://act-rules.github.io/rules/23a2a8
Accessibility Insights rule:
Rule: image-alt
Check: alt-attribute
Guidance: https://accessibilityinsights.io/info-examples/web/image-alt/
When multiple tools flag the same issue, list all rule IDs so the report can be linked to automated check output.
2.7 Severity
Rate severity using a standard scale. Use this taxonomy consistently across all reports in a project.
| Level | Definition | Example |
|---|---|---|
| Critical | Users cannot complete a core task at all | Modal dialog traps keyboard focus and has no close mechanism |
| High | Significant barrier that degrades or blocks a key workflow | Form error messages not announced to screen readers |
| Medium | Noticeable barrier with a workaround available | Focus indicator is missing but Tab order is logical |
| Low | Minor issue with minimal real-world impact | Decorative icon has a redundant aria-label |
This taxonomy aligns with the severity levels used in ACCESSIBILITY.md.
2.8 Frequency / Occurrence
Report how often the element appears on the page and across the site.
Frequency: 1 instance on this page
Frequency: Appears on every page in the main navigation
Frequency: Found on 23 of 47 pages crawled (49%)
For automated scans, include aggregate counts:
{
"rule": "image-alt",
"occurrences": 14,
"pages_affected": 6,
"total_pages_scanned": 50
}
Frequency informs prioritisation. A single critical failure may warrant immediate action; a low-severity issue across hundreds of pages may need a systematic fix.
Frequency amplifies effective severity. A “Low” rated issue that appears on every page of a site, or on a heavily trafficked page or a top user task (such as sign-in, checkout, or search), should be treated with higher urgency than its base severity suggests. When assigning priority, consider both the raw severity rating and the reach of the issue:
| Situation | Suggested priority adjustment |
|---|---|
| Low severity, appears on every page | Treat as Medium |
| Medium severity, appears on every page | Treat as High |
| Low/Medium severity, on a top-task page | Escalate by one severity level |
| Low/Medium severity, on a high-traffic landing page | Escalate by one severity level |
3. Recommended Additional Fields
These fields are not always required but significantly improve report quality.
3.1 Issue Summary (Title)
Write a concise title that identifies:
- The component type
- The failure mode
- The WCAG criterion (optionally)
Good titles:
Close button missing accessible name (WCAG 1.1.1)Error message not associated with form field (WCAG 1.3.1)Dropdown menu items not keyboard-operable (WCAG 2.1.1)
Avoid:
Accessibility issue foundScreen reader problem on checkout
3.2 Description
Explain what is wrong and why it is a barrier for users with disabilities.
The "Close" button in the cookie-consent dialog contains only a decorative SVG icon
with no alt text, aria-label, or visible text. Screen reader users hear "button" with
no indication of its purpose, making it impossible to dismiss the dialog by voice or
from a screen reader virtual cursor.
3.3 Steps to Reproduce
Provide numbered steps a developer can follow to see the failure.
1. Go to https://example.com/shop
2. Wait for the cookie consent banner to appear
3. Open NVDA (or VoiceOver on macOS)
4. Press Tab to move focus to the "X" button in the banner
5. Listen to what the screen reader announces
For automated test output, include the command used and relevant configuration:
npx axe https://example.com/shop --tags wcag2a,wcag2aa --reporter json
3.4 Expected Behaviour
State what the correct experience should be.
Expected: Screen reader announces "Close cookie consent dialog, button"
or equivalent meaningful label.
3.5 Actual Behaviour
State what currently happens.
Actual: Screen reader announces "button" only. No accessible name is provided.
3.6 Testing Environment
Specify the full stack used during testing. AT bugs are often environment-specific.
Browser: Chrome 124 / Firefox 126 / Safari 17.4
OS: Windows 11 / macOS 14 / iOS 17
Screen reader: NVDA 2024.1 / JAWS 2024 / VoiceOver
Zoom level: 100% / 200%
Tool: axe-core 4.9.1 / Accessibility Insights 2.47 / Pa11y 6.2
3.7 Impact Statement
Describe which disability groups are affected and how.
Impact: Blind and low-vision users relying on screen readers cannot identify
or operate the close button. Users who rely on voice control software
(e.g. Dragon NaturallySpeaking) cannot activate an unnamed button by
speaking its label.
3.8 Suggested Fix
Provide a concrete remediation if possible.
<!-- Current (broken) -->
<button class="close-btn">
<svg aria-hidden="true" ...></svg>
</button>
<!-- Fixed: Option A — aria-label -->
<button class="close-btn" aria-label="Close cookie consent dialog">
<svg aria-hidden="true" ...></svg>
</button>
<!-- Fixed: Option B — visually hidden text -->
<button class="close-btn">
<svg aria-hidden="true" ...></svg>
<span class="visually-hidden">Close cookie consent dialog</span>
</button>
3.9 Personalisation and Context
Accessibility failures are not always reproducible on every page load. Many issues only surface under specific user settings, device types, or navigation paths. Always record the personalisation state and context at the time of discovery.
Colour scheme and display preferences
Note whether the issue occurs in light mode, dark mode, or both, and whether any OS-level display settings are active.
Colour scheme: Light mode / Dark mode / System default
Forced Colors: Active (Windows High Contrast) / Inactive
Contrast mode: Standard / Increased contrast (macOS)
CSS media features
Some failures only appear when specific CSS media features are active. Record any active preferences.
prefers-color-scheme: light / dark
prefers-reduced-motion: reduce / no-preference
prefers-contrast: more / less / forced / no-preference
forced-colors: active / none
See the Light/Dark Mode Accessibility Best Practices guide for more detail.
Viewport and device type
Layout, component visibility, and interaction patterns often differ between mobile and desktop. Specify the device context when the issue is viewport-specific.
Viewport: 375 × 812 px (iPhone 14, Safari iOS 17)
Viewport: 1440 × 900 px (MacBook Pro, Chrome 124)
Device: Mobile — iOS 17 / Android 14
Device: Desktop — Windows 11 / macOS 14
Orientation: Portrait / Landscape
Navigation path and UI state
The route taken to reach a page — and any UI interactions performed before the failure — can determine whether a problem appears at all. Record the user journey when it is relevant.
Arrived via: Direct URL / Search results link / Internal navigation from [page]
Preceded by: Clicked "Add to cart" on product page, then navigated to checkout
UI state: Modal open / Accordion expanded / Dropdown active
Login state: Authenticated (standard user role) / Guest / Admin
Together, these contextual details help developers reproduce failures that only appear under specific personalisation settings, on specific device classes, or after specific interaction sequences.
4. Structured Report Templates
4.1 Markdown Template (for GitHub Issues)
## Accessibility Issue: [Brief Description]
**Bug ID:** `[PREFIX-xxxxxxxx]` (instance) / `[PREFIX-xxxxxxxx]` (pattern)
**URL:** [Full URL where issue was found]
**XPath:** `[Shortest unique XPath]`
**Full DOM path:** `[Full ancestor chain XPath]`
**WCAG SC:** [SC number] — [SC name] (Level [A/AA/AAA])
**Rule:** [Tool name] — [Rule ID]
**Severity:** [Critical / High / Medium / Low]
**Frequency:** [Number of instances; pages affected]
**Screen type:** [desktop / mobile] | **Colour mode:** [light / dark]
### HTML Snippet
```html
[Minimal failing HTML fragment]
Description
[Explain what is wrong and why it creates a barrier]
Steps to Reproduce
- [Step 1]
- [Step 2]
- [Step 3]
Expected Behaviour
[What should happen]
Actual Behaviour
[What currently happens]
Testing Environment
| Item | Value |
|---|---|
| Browser | [name and version] |
| OS | [name and version] |
| Screen reader | [name and version, or N/A] |
| Testing tool | [name and version] |
Impact
[Who is affected and how]
Suggested Fix (optional)
[Code or prose describing the fix]
### 4.2 JSON Schema for Automated Tool Output
Use this schema when scripts or CI pipelines emit machine-readable accessibility reports.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "AccessibilityIssue",
"type": "object",
"required": ["url", "wcag_sc", "severity", "rule_id", "xpath", "html_snippet"],
"properties": {
"instance_id": {
"type": "string",
"pattern": "^[A-Z0-9]+-[0-9a-f]{8}$",
"examples": ["DRU-a3f1b2c4"],
"description": "Stable identifier for this specific violation on this page. Hash of page path + CSS selector + rule ID + screen type. Same element, same rule, same page, same viewport class = same ID."
},
"pattern_id": {
"type": "string",
"pattern": "^[A-Z0-9]+-[0-9a-f]{8}$",
"examples": ["DRU-f7e3a1b2"],
"description": "Stable identifier for the underlying template-level pattern across pages. Hash of CSS selector + rule ID + screen type (no page path). Multiple pages sharing the same component will share this ID."
},
"screen_type": {
"type": "string",
"enum": ["desktop", "mobile"],
"description": "Inferred from viewport width: < 768 px = mobile, >= 768 px = desktop. Uses the MOBILE_BREAKPOINT constant (768) defined in the implementation."
},
"color_mode": {
"type": "string",
"enum": ["light", "dark"],
"default": "light",
"description": "Colour scheme active during the scan. Dark mode support is a future extension; default to 'light'."
},
"url": {
"type": "string",
"format": "uri",
"description": "Full URL of the page where the issue was found"
},
"xpath": {
"type": "string",
"description": "Shortest unique XPath identifying the failing element"
},
"xpath_full": {
"type": "string",
"description": "Full ancestor-chain XPath for unambiguous DOM location"
},
"html_snippet": {
"type": "string",
"description": "Minimal HTML fragment demonstrating the failure"
},
"wcag_sc": {
"type": "string",
"pattern": "^\\d\\.\\d+\\.\\d+$",
"examples": ["1.1.1", "4.1.2", "1.4.3"],
"description": "WCAG Success Criterion number"
},
"wcag_level": {
"type": "string",
"enum": ["A", "AA", "AAA"],
"description": "WCAG conformance level"
},
"rule_id": {
"type": "string",
"description": "Rule identifier from the testing tool (e.g. axe-core rule ID)"
},
"act_rule_id": {
"type": "string",
"description": "W3C ACT rule identifier, if applicable"
},
"tool": {
"type": "string",
"description": "Name and version of the testing tool that found the issue"
},
"severity": {
"type": "string",
"enum": ["critical", "high", "medium", "low"],
"description": "Severity level of the issue"
},
"frequency": {
"type": "object",
"properties": {
"instances_on_page": { "type": "integer" },
"pages_affected": { "type": "integer" },
"total_pages_scanned": { "type": "integer" }
},
"description": "How often the issue occurs"
},
"summary": {
"type": "string",
"description": "Short title: component + failure mode + criterion"
},
"description": {
"type": "string",
"description": "Human-readable explanation of the barrier"
},
"impact": {
"type": "array",
"items": { "type": "string" },
"examples": [["blind", "low-vision", "motor"]],
"description": "Disability groups affected"
},
"environment": {
"type": "object",
"properties": {
"browser": { "type": "string" },
"os": { "type": "string" },
"screen_reader": { "type": "string" },
"zoom_level": { "type": "string" }
}
},
"suggested_fix": {
"type": "string",
"description": "Code snippet or prose describing the remediation"
},
"steps_to_reproduce": {
"type": "array",
"items": { "type": "string" },
"description": "Ordered steps to observe the failure"
}
}
}
4.3 Example JSON Report (axe-core output mapped to schema)
{
"instance_id": "DRU-a3f1b2c4",
"pattern_id": "DRU-f7e3a1b2",
"screen_type": "desktop",
"color_mode": "light",
"url": "https://example.com/checkout?step=2",
"xpath": "//button[contains(@class,'close-btn')]",
"xpath_full": "/html/body/div[@id='cookie-banner']/button[contains(@class,'close-btn')]",
"html_snippet": "<button class=\"close-btn\"><svg aria-hidden=\"true\"></svg></button>",
"wcag_sc": "4.1.2",
"wcag_level": "A",
"rule_id": "button-name",
"act_rule_id": "97a4e1",
"tool": "axe-core 4.9.1",
"severity": "critical",
"frequency": {
"instances_on_page": 1,
"pages_affected": 12,
"total_pages_scanned": 50
},
"summary": "Close button missing accessible name (WCAG 4.1.2)",
"description": "The cookie consent close button contains only an SVG icon with no accessible name. Screen reader users hear 'button' with no indication of the button's purpose.",
"impact": ["blind", "low-vision", "voice-control"],
"environment": {
"browser": "Chrome 124",
"os": "Windows 11",
"screen_reader": "NVDA 2024.1",
"zoom_level": "100%"
},
"suggested_fix": "<button class=\"close-btn\" aria-label=\"Close cookie consent dialog\"><svg aria-hidden=\"true\"></svg></button>",
"steps_to_reproduce": [
"Go to https://example.com/checkout?step=2",
"Wait for the cookie consent banner to appear",
"Open NVDA and press Tab to reach the close button",
"Observe that NVDA announces 'button' with no label"
]
}
4.4 EARL: Evaluation and Report Language
EARL (Evaluation and Report Language) is a W3C standard for expressing test results in a machine-readable format. It uses RDF (Resource Description Framework) to describe which subject (a web page or resource) was tested, which assertion (a WCAG criterion or rule) was evaluated, and what the outcome was.
Use EARL when you need interoperable, tool-agnostic accessibility reports that can be processed by different systems — for example, aggregating results from multiple testing tools, feeding a compliance dashboard, or archiving audit evidence.
Minimal EARL example (Turtle syntax):
@prefix earl: <http://www.w3.org/ns/earl#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
[] a earl:Assertion ;
earl:assertedBy <https://example.com/tools/axe-runner> ;
earl:subject <https://example.com/checkout?step=2> ;
earl:test <http://www.w3.org/TR/WCAG21/#non-text-content> ;
earl:result [
a earl:TestResult ;
earl:outcome earl:failed ;
dcterms:description "Close button contains only an SVG icon with no accessible name." ;
dcterms:date "2025-06-01T10:30:00Z"^^xsd:dateTime
] ;
earl:mode earl:automatic .
Key EARL concepts:
| Term | Meaning |
|---|---|
earl:Assertion |
A single test result (subject + test + outcome) |
earl:subject |
The URL or resource that was tested |
earl:test |
The criterion or rule being tested (WCAG SC, ACT rule, etc.) |
earl:outcome |
earl:passed, earl:failed, earl:cantTell, earl:inapplicable, or earl:untested |
earl:mode |
How the test was performed: earl:automatic, earl:manual, or earl:semiAuto |
earl:assertedBy |
The tool or person that produced the assertion |
EARL output can be produced by tools such as Alfa, Axe Reporter EARL, and ACT-Rules implementations. The W3C WCAG-EM Report Tool also exports EARL.
For further reading, see EARL overview on W3C WAI.
5. Guidance for Automated Tools and AI Agents
When a script or AI agent generates accessibility reports, apply these rules.
5.1 Always Emit Both XPath Forms
Automated tools must emit both the simplified XPath (shortest unique selector) and the full DOM path XPath. The simplified form aids human readability; the full form aids deterministic replay.
5.2 Deduplicate Before Reporting
Before filing issues, group violations by rule and normalised XPath. Reporting 200 identical image-alt violations as separate issues obscures the true scope of the problem. Instead, report one issue with frequency.instances_on_page set to 200.
# Pseudocode: aggregate duplicates
issues = defaultdict(list)
for violation in raw_results:
key = (violation["rule_id"], violation["wcag_sc"], normalize_xpath(violation["xpath"]))
issues[key].append(violation)
for key, group in issues.items():
report_issue(group[0], frequency=len(group))
5.3 Map Tool Output to WCAG Criteria
Every automated rule should map to at least one WCAG SC. Maintain a rule-to-WCAG mapping table in your project (see AXE_RULES_COVERAGE.md for an example) so that reports consistently include the criterion.
5.4 Preserve the HTML Snippet
Extract the failing element’s outer HTML at scan time. DOM content may change after the scan. The preserved snippet allows verification of whether a deployed fix addresses the original issue.
5.5 Assign Severity Consistently
Use a single severity taxonomy across all reports and all tools. If multiple tools report the same violation at different severities, use the higher severity.
5.6 Include Confidence Score for Automated Detections
Automated tools sometimes flag potential violations that require manual confirmation (e.g. colour contrast where the background is a gradient). Include a confidence field when the tool provides it.
{
"rule_id": "color-contrast",
"confidence": "needs-review",
"note": "Background colour is a CSS gradient; automated contrast check may be inaccurate. Manual verification required."
}
5.7 AI Agent Prompt for Issue Filing
When an AI agent files a GitHub Issue from automated scan output, use the following prompt structure:
You are an accessibility engineer filing a GitHub Issue.
Given the following JSON violation report, create a well-structured
GitHub Issue using the Markdown template below.
Rules:
- Use the issue summary as the title.
- Populate all fields; write "N/A" only if the data is genuinely absent.
- Do not paraphrase WCAG criterion names; use the exact W3C wording.
- Include the HTML snippet in a fenced code block.
- Add the label "accessibility" to the issue.
Violation data:
[INSERT JSON HERE]
Template:
[INSERT MARKDOWN TEMPLATE HERE]
5.8 Generate Stable Unique Identifiers for Violations
Assigning a stable identifier to each violation enables automated pipelines to track whether a fix was deployed, detect regressions, and deduplicate results across multiple scan runs.
Two levels of identification
| Level | Inputs to hash | Purpose |
|---|---|---|
instance_id |
page path + CSS selector + rule ID + screen type | Uniquely identifies one occurrence of a violation on one specific page. Same element, same rule, same page, same viewport class = same ID. |
pattern_id |
CSS selector + rule ID + screen type | Identifies the recurring template-level pattern across pages. Multiple pages sharing the same component will share a pattern_id. |
Identifier format: [PREFIX]-[8-char-hex] (e.g. DRU-a3f1b2c4)
The prefix is project-specific (e.g. DRU for a Drupal site, A11Y as a generic default) and can be set in your scanner configuration.
Screen type detection
Infer screen type from the viewport width reported in the axe-core result. The MOBILE_BREAKPOINT constant is defined in the implementation block below; anything under 768 px is treated as mobile.
Colour mode
Default to light for current scans. Dark mode is a placeholder for future support when testing toolchains support prefers-color-scheme: dark emulation.
JavaScript implementation (Node.js, using crypto)
const crypto = require('crypto');
/** Viewport width threshold below which a device is treated as mobile. */
const MOBILE_BREAKPOINT = 768;
/**
* Detect screen type from an axe-core viewport object.
* @param {{ width: number }} viewport
* @returns {'mobile'|'desktop'}
*/
function detectScreenType(viewport) {
return viewport && viewport.width < MOBILE_BREAKPOINT ? 'mobile' : 'desktop';
}
/**
* Normalise an axe-core node.target value to a single CSS selector string.
* @param {string|string[]} target - axe-core node.target
* @returns {string}
*/
function normalizeSelector(target) {
return Array.isArray(target) ? target.join(' > ') : String(target);
}
/**
* Generate a stable instance ID for one violation on one page.
* Uses SHA-256; only the first 8 hex characters are kept.
* @param {string} pagePath - URL path (e.g. '/checkout')
* @param {string} selector - CSS selector of the failing element
* @param {string} ruleId - axe-core rule ID (e.g. 'button-name')
* @param {string} screenType - 'desktop' or 'mobile'
* @param {string} [prefix='A11Y'] - Project prefix (e.g. 'DRU')
* @returns {string} e.g. 'DRU-a3f1b2c4'
*/
function generateInstanceId(pagePath, selector, ruleId, screenType, prefix = 'A11Y') {
const input = `${pagePath}|${selector}|${ruleId}|${screenType}`;
const hash = crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
return `${prefix}-${hash}`;
}
/**
* Generate a pattern ID that identifies the same issue across multiple pages.
* Uses SHA-256; only the first 8 hex characters are kept.
* @param {string} selector - CSS selector of the failing element
* @param {string} ruleId - axe-core rule ID
* @param {string} screenType - 'desktop' or 'mobile'
* @param {string} [prefix='A11Y'] - Project prefix
* @returns {string} e.g. 'DRU-f7e3a1b2'
*/
function generatePatternId(selector, ruleId, screenType, prefix = 'A11Y') {
const input = `${selector}|${ruleId}|${screenType}`;
const hash = crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
return `${prefix}-${hash}`;
}
Annotating axe-core results
const { URL } = require('url');
/**
* Annotate every violation node in an axe-core results object
* with instance_id, pattern_id, screen_type, and color_mode.
*
* @param {object} axeResults - Full axe.run() result object
* @param {string} [prefix='A11Y'] - Project prefix for IDs
* @param {string} [colorMode='light'] - 'light' or 'dark' (dark mode: future extension)
* @returns {Array<object>} Flat array of annotated violation nodes
*/
function annotateViolations(axeResults, prefix = 'A11Y', colorMode = 'light') {
const pagePath = new URL(axeResults.url).pathname;
const viewport = { width: axeResults.testEnvironment.windowWidth };
const screenType = detectScreenType(viewport);
return axeResults.violations.flatMap(violation =>
violation.nodes.map(node => {
const selector = normalizeSelector(node.target);
return {
...node,
rule_id: violation.id,
instance_id: generateInstanceId(pagePath, selector, violation.id, screenType, prefix),
pattern_id: generatePatternId(selector, violation.id, screenType, prefix),
screen_type: screenType,
color_mode: colorMode,
};
})
);
}
Example output (single annotated node)
{
"instance_id": "DRU-a3f1b2c4",
"pattern_id": "DRU-f7e3a1b2",
"screen_type": "desktop",
"color_mode": "light",
"rule_id": "button-name",
"target": [".cookie-banner > button.close-btn"],
"html": "<button class=\"close-btn\"><svg aria-hidden=\"true\"></svg></button>"
}
Why CSS selector, not XPath?
The CSS selector (from node.target) is used in the hash rather than the XPath because:
- axe-core reports CSS selectors natively via
node.target - CSS selectors are stable across XPath conversion logic changes
- The hash remains consistent even if XPath generation evolves
Include both the CSS selector and XPath in the report; use only the CSS selector as the hash input.
6. Reporting Accessibility Issues to External Organisations
When reporting accessibility barriers to a third-party organisation (a vendor, government service, or public website), adapt the report to the audience.
6.1 Contact Strategy
- Check for an existing channel — Look for an accessibility statement, a dedicated
accessibility@email address, or a feedback form. - Be specific, not general — “Your site is inaccessible” does not help. Provide the exact page, element, and barrier.
- Focus on impact — Explain who is affected and what task they cannot complete, rather than leading with the technical violation.
- Reference WCAG — Cite the specific criterion. For public-sector organisations, reference applicable legislation (e.g. Section 508 in the US, EN 301 549 in the EU, AODA in Ontario).
- Offer to help — Suggest the fix. Organisations are more likely to act quickly when a solution is provided alongside the problem.
- Set a timeline — Request acknowledgement within a defined period (e.g. 10 business days) and a remediation timeline for critical issues.
- Escalate if necessary — If no response is received, contact the relevant accessibility enforcement body or ombudsman.
6.2 Template for External Reports
Subject: Accessibility barrier on [Page Title] — [WCAG SC]
Dear [Organisation Name] Accessibility Team,
I am writing to report an accessibility barrier I encountered on your website.
Page: [Full URL]
Issue: [One-sentence description of the barrier]
Impact: [Who is affected and what they cannot do]
WCAG SC: [SC number and name, Level]
Steps to reproduce:
1. [Step 1]
2. [Step 2]
Expected: [What should happen]
Actual: [What happens instead]
Suggested fix: [Brief description or code snippet if appropriate]
I am available to provide further information or test a proposed fix.
I would appreciate acknowledgement within 10 business days and a
remediation timeline for this critical barrier.
Thank you for your attention to this matter.
[Your name / organisation]
7. Quality Checklist
Before filing or submitting an accessibility bug report, verify each item:
- URL is exact and publicly accessible (or a test account is provided)
- Unique bug identifiers (
instance_idandpattern_id) are generated and included - Screen type (
desktop/mobile) and colour mode (light/dark) are recorded - XPath (simplified) uniquely identifies the element
- Full DOM path XPath is included
- HTML snippet is minimal and self-contained
- WCAG SC is cited with the correct level (A/AA/AAA)
- Automated rule ID is included (axe-core, ACT, or tool-specific)
- Severity is assigned using the project’s standard taxonomy
- Frequency / occurrence count is provided
- Summary title follows the
[Component] — [Failure] ([WCAG SC])pattern - Steps to reproduce are numbered and complete
- Expected and actual behaviours are stated separately
- Testing environment (browser, OS, AT, tool) is documented
- Personalisation context is recorded (colour scheme, CSS media features, viewport, navigation path)
- Impact on specific disability groups is described
- Duplicate violations are aggregated, not filed individually
- For automated output: confidence level is included where relevant
8. WCAG Success Criteria Quick Reference
The following criteria are most frequently violated in automated scans. Use these as a starting point when assigning wcag_sc.
| SC | Name | Level | Common Violations |
|---|---|---|---|
| 1.1.1 | Non-text Content | A | Missing alt on images, unlabelled icon buttons |
| 1.3.1 | Info and Relationships | A | Unsemantic heading structure, missing form labels |
| 1.3.3 | Sensory Characteristics | A | Instructions that rely on shape, colour, or position only |
| 1.4.1 | Use of Color | A | Status conveyed by colour alone |
| 1.4.3 | Contrast (Minimum) | AA | Text below 4.5:1 contrast ratio |
| 1.4.4 | Resize Text | AA | Page breaks at 200% zoom |
| 1.4.11 | Non-text Contrast | AA | UI component borders below 3:1 contrast |
| 2.1.1 | Keyboard | A | Elements not reachable or operable by keyboard |
| 2.4.3 | Focus Order | A | Illogical tab order, focus moves unexpectedly |
| 2.4.7 | Focus Visible | AA | No visible focus indicator |
| 3.3.1 | Error Identification | A | Form errors not described in text |
| 3.3.2 | Labels or Instructions | A | Form fields without visible labels |
| 4.1.2 | Name, Role, Value | A | Custom widgets missing ARIA name, role, or state |
| 4.1.3 | Status Messages | AA | Notifications not exposed to screen readers |
For the complete list, see the WCAG 2.2 Quick Reference.
9. Further Reading
The following resources informed this guide:
- Writing Impactful Accessibility Reports — Mike Gifford, OpenConcept
- How to Fix Accessibility Bugs — Mike Gifford, GitHub README Guides
- Contacting Organizations about Inaccessible Websites — W3C WAI
- How to Report Accessibility Bugs — DigitalA11y
- Template: Reporting Accessibility Issues — Harvard University
- Accessibility Insights for Web — Microsoft
- ACT Rules Community Group — W3C
- EARL: Evaluation and Report Language — W3C WAI (machine-readable standard for expressing accessibility test results)
- AXE Rules Coverage — This repository
- Manual Accessibility Testing Guide — This repository