bug-reporting — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bug-reporting (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Canonical source:examples/ACCESSIBILITY_BUG_REPORTING_BEST_PRACTICES.mdinmgifford/ACCESSIBILITY.mdThis skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules when filing, reviewing, or generating accessibility bug reports from any source — automated tool output, manual testing, or AI agent scans.
Reproducibility is the single most important quality of a bug report. If a developer cannot reproduce the problem, they cannot fix it.
Every report must give enough information for a developer who was not present during testing to reproduce, understand, and fix the problem without additional back-and-forth.
| Level | Meaning | Action |
|---|---|---|
| Critical | Users cannot complete a core task at all | Must fix before release |
| High | Significant barrier that degrades or blocks a key workflow | Fix urgently |
| Medium | Noticeable barrier with a workaround available | Fix in near-term backlog |
| Low | Minor issue with minimal real-world impact | Fix when convenient |
Every accessibility bug report must include all eight of the following fields. Omitting any required field is Critical — the report cannot be acted on.
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-formIf the issue appears on multiple pages, list all affected URLs or describe the URL pattern (e.g. "all /product/* pages").
Provide the shortest XPath that uniquely identifies the failing element. Prefer attribute-based selectors over absolute paths.
//button[@id="submit-payment"]
//input[@name="card-number"]
//div[@role="dialog"][@aria-labelledby="modal-title"]Provide the complete ancestor chain for disambiguation 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 must emit both XPath forms. The full path supports debugging in environments where IDs change dynamically (e.g. React, Angular).
Include the minimal HTML fragment that demonstrates the problem. Trim surrounding markup to the smallest context that still shows the issue.
<!-- Bad: no accessible name -->
<button>
<img src="close.svg">
</button>
<!-- Good: accessible name provided -->
<button>
<img src="close.svg" alt="Close dialog">
</button>Include parent elements only when the violation depends on the parent–child relationship (e.g. missing <label> for <input>).
Cite the specific WCAG 2.1 or 2.2 Success Criterion violated, including its level.
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)Reference the rule from the testing tool or from the W3C ACT framework.
Rule ID: image-alt
Rule: Images must have alternate text
Tool: axe-core 4.x
ACT Rule: 23a2a8 — Image button has accessible name
URL: https://act-rules.github.io/rules/23a2a8When multiple tools flag the same issue, list all rule IDs.
Assign a severity level using the taxonomy above. Use it consistently across all reports in a project. When multiple tools report the same violation at different severities, use the higher severity.
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 on every page or a top user task (sign-in, checkout, search) should be treated with higher urgency than its base severity suggests.
| 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 |
These fields are not always required but significantly improve report quality. Omitting them is a High severity gap when the issue is complex or the audience is unfamiliar with the codebase.
Write a concise title: [Component] — [Failure mode] ([WCAG SC]).
Good:
Close button missing accessible name (WCAG 1.1.1)Error message not associated with form field (WCAG 1.3.1)Avoid:
Accessibility issue foundScreen reader problem on checkoutExplain 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.Number the 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 announcesFor automated test output, include the command used:
npx axe https://example.com/shop --tags wcag2a,wcag2aa --reporter jsonState what the correct experience should be.
Expected: Screen reader announces "Close cookie consent dialog, button"
or equivalent meaningful label.State what currently happens.
Actual: Screen reader announces "button" only. No accessible name is provided.Specify the full stack. 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.2Describe 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.Provide a concrete remediation when 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>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
Colour scheme: Light mode / Dark mode / System default
Forced Colors: Active (Windows High Contrast) / Inactive
Contrast mode: Standard / Increased contrast (macOS)CSS media features
prefers-color-scheme: light / dark
prefers-reduced-motion: reduce / no-preference
prefers-contrast: more / less / forced / no-preference
forced-colors: active / noneViewport and device type
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 / LandscapeNavigation path and UI state
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## 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
[Minimal failing HTML fragment]
### Description
[Explain what is wrong and why it creates a barrier]
### Steps to Reproduce
1. [Step 1]
2. [Step 2]
3. [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]Use this schema when scripts or CI pipelines emit machine-readable accessibility reports.
{
"$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."
},
"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)."
},
"screen_type": { "type": "string", "enum": ["desktop", "mobile"] },
"color_mode": { "type": "string", "enum": ["light", "dark"], "default": "light" },
"url": { "type": "string", "format": "uri" },
"xpath": { "type": "string" },
"xpath_full": { "type": "string" },
"html_snippet": { "type": "string" },
"wcag_sc": { "type": "string", "pattern": "^\\d\\.\\d+\\.\\d+$" },
"wcag_level": { "type": "string", "enum": ["A", "AA", "AAA"] },
"rule_id": { "type": "string" },
"act_rule_id": { "type": "string" },
"tool": { "type": "string" },
"severity": { "type": "string", "enum": ["critical", "high", "medium", "low"] },
"frequency": {
"type": "object",
"properties": {
"instances_on_page": { "type": "integer" },
"pages_affected": { "type": "integer" },
"total_pages_scanned": { "type": "integer" }
}
},
"summary": { "type": "string" },
"description": { "type": "string" },
"impact": { "type": "array", "items": { "type": "string" } },
"environment": {
"type": "object",
"properties": {
"browser": { "type": "string" },
"os": { "type": "string" },
"screen_reader": { "type": "string" },
"zoom_level": { "type": "string" }
}
},
"suggested_fix": { "type": "string" },
"steps_to_reproduce": { "type": "array", "items": { "type": "string" } }
}
}{
"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.",
"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"
]
}EARL (Evaluation and Report Language) is a W3C standard for expressing test results in a machine-readable format. Use EARL when you need interoperable, tool-agnostic reports that can be processed by different systems — for example, aggregating results from multiple tools, feeding a compliance dashboard, or archiving audit evidence.
@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 .| 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 Alfa, Axe Reporter EARL, and ACT-Rules implementations. The W3C WCAG-EM Report Tool also exports EARL.
Apply these additional rules when scripts or AI agents generate reports.
Emit both the simplified XPath (shortest unique selector) and the full DOM path XPath in every violation. The simplified form aids human readability; the full form aids deterministic replay.
Group violations by rule and normalised XPath before filing. Report one issue with frequency.instances_on_page set to the count rather than filing hundreds of identical issues.
# 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))Every automated rule should map to at least one WCAG SC. Maintain a rule-to-WCAG mapping table in your project so that reports consistently include the criterion.
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.
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.
Include a confidence field when the tool provides it, especially for violations that require manual confirmation (e.g. colour contrast over a gradient):
{
"rule_id": "color-contrast",
"confidence": "needs-review",
"note": "Background colour is a CSS gradient; automated contrast check may be inaccurate. Manual verification required."
}When an AI agent files a GitHub Issue from scan output, use this 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 above.
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]Assigning a stable identifier to each violation enables automated pipelines to track fixes, detect regressions, and deduplicate across scan runs.
Two levels of identification
| Level | Inputs to hash | Purpose |
|---|---|---|
instance_id | page path + CSS selector + rule ID + screen type | Identifies one occurrence on one specific page |
pattern_id | CSS selector + rule ID + screen type | Identifies the recurring pattern across pages |
Identifier format: [PREFIX]-[8-char-hex] (e.g. DRU-a3f1b2c4)
The prefix is project-specific (e.g. DRU for Drupal, A11Y as a generic default). Infer screen type from viewport width: < 768 px = mobile, >= 768 px = desktop. Default color_mode to light.
JavaScript implementation (Node.js)
const crypto = require('crypto');
const MOBILE_BREAKPOINT = 768;
function detectScreenType(viewport) {
return viewport && viewport.width < MOBILE_BREAKPOINT ? 'mobile' : 'desktop';
}
function normalizeSelector(target) {
return Array.isArray(target) ? target.join(' > ') : String(target);
}
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}`;
}
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}`;
}
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,
};
})
);
}The CSS selector (from node.target) is used in the hash rather than the XPath because axe-core reports it natively and it is stable across XPath conversion logic changes. Include both in the report; use only the CSS selector as the hash input.
When reporting accessibility barriers to a third-party (vendor, government, public website), adapt the report to the audience:
accessibility@ email, or feedback form.
legislation (Section 508, EN 301 549, AODA, etc.).
accompanies the problem.
10 business days) and a remediation timeline for critical issues.
if no response is received.
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]Before filing or submitting an accessibility bug report, verify each item:
instance_id and pattern_id) are generated and includeddesktop / mobile) and colour mode (light / dark) are recorded[Component] — [Failure] ([WCAG SC]) pattern| 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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.