Project Overview
axe-core is an open-source accessibility testing engine for web interfaces. It embeds a set of automated rules that detect common WCAG 2.x and ARIA violations, enabling developers and QA teams to catch accessibility issues early in their development and CI workflows.
Why axe-core Exists
- Automate detection of accessibility defects during development and testing
- Promote consistent implementation of WCAG guidelines
- Integrate seamlessly into unit, integration, and end-to-end tests
Problems axe-core Solves
- Manual reviews that miss edge cases or regressions
- Inconsistencies across browsers and testing tools
- Time-consuming audits by streamlining rule execution and reporting
Supported Runtimes & Browsers
Browsers
- Chrome (Desktop & Headless)
- Firefox (Desktop & Headless)
- Safari
- Microsoft Edge
Runtimes
- Browser (via
<script>
or bundlers like Webpack/Rollup) - Node.js (for server-side rendering checks)
- Headless automation (Puppeteer, Playwright)
Example: Running axe-core in Puppeteer
const puppeteer = require('puppeteer');
const { configureAxe, getAxeResults } = require('axe-puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Initialize axe-core in the page context
await configureAxe(page).configure({ rules: [{ id: 'color-contrast', enabled: true }] });
const results = await getAxeResults(page);
console.log(results.violations);
await browser.close();
})();
Community & Resources
- Contribute via GitHub: https://github.com/dequelabs/axe-core
- Report security issues: see SECURITY.md
- Review project updates in CHANGELOG.md
- Follow the Code of Conduct: code-of-conduct.md
- Join discussions on Accessibility SLACK: https://deque.com/slack
License
axe-core is licensed under the Mozilla Public License, version 2.0. See LICENSE for full terms and contributor rights.
Getting Started
Get your first accessibility scan running in the browser, in Node, and in CI within minutes.
1. Installation
Via npm (Node & Bundlers)
Install axe-core as a dev-dependency:
npm install --save-dev axe-core
Via CDN (Browser)
Include the latest bundle from unpkg:
<script src="https://unpkg.com/axe-core@4/dist/axe.min.js"></script>
2. Browser Quick Start
Run an audit on page load and log violations:
<!doctype html>
<html>
<head>
<script src="https://unpkg.com/axe-core@4/dist/axe.min.js"></script>
</head>
<body>
<!-- your page markup -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// Scan entire document
axe.run()
.then(results => {
if (results.violations.length) {
console.error('Accessibility violations:', results.violations);
} else {
console.log('No accessibility violations found');
}
})
.catch(err => console.error('axe.run error:', err));
});
</script>
</body>
</html>
3. Node.js Quick Start with Puppeteer
Use Puppeteer to load a page, inject axe-core, and run an audit headlessly.
- Create
scan.js
:
const puppeteer = require('puppeteer');
const axeSource = require('axe-core').source;
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://your-site.example.com');
// Inject axe-core into the page context
await page.evaluate(axeSource);
// Run axe and retrieve results
const results = await page.evaluate(async () => {
return await axe.run(document, {
runOnly: { type: 'tag', values: ['wcag2aa'] }
});
});
if (results.violations.length) {
console.error('Accessibility issues:', results.violations);
process.exit(1);
} else {
console.log('No accessibility violations found');
}
await browser.close();
})();
- Run the script:
node scan.js
4. Continuous Integration
In CI, install dependencies, run your scan script, and fail the build on violations.
Example: GitHub Actions
Create .github/workflows/accessibility.yml
:
name: Accessibility Scan
on: [push, pull_request]
jobs:
axe-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Run accessibility scan
run: node scan.js
Example: npm Script
Add to your package.json
:
"scripts": {
"accessibility": "node scan.js"
}
Then in any CI job:
npm run accessibility
Next Steps
- Scope audits to specific selectors (
axe.run('#main', {...})
) to speed up checks. - Customize rules in options (e.g.
rules: { 'color-contrast': { enabled: false } }
). - Integrate reports into dashboards by supplying a custom
reporter
function.
Core Concepts
Axe-core models accessibility rules as JavaScript objects that pair DOM‐level selectors with test functions (“checks”), runs them on a flattened virtual DOM, and classifies outcomes using shared constants. Understanding this model lets you read, extend, or optimize axe-core rules and checks.
Rule Definition
A rule object drives element selection and check execution:
// Example: lib/rules/image-alt.js
export const imageAlt = {
id: 'image-alt',
selector: 'img', // CSS selector for candidates
matches: (node) => !node.hasAttribute('role'),
any: ['has-alt-text'], // pass if any of these checks pass
all: [], // run all checks must pass
none: ['is-hidden'], // fail if any of these checks pass
metadata: {
description: 'Images must have alternate text',
help: 'Provide meaningful alt text for images'
},
tags: ['wcag2a', 'best-practice']
};
Properties:
selector
(string): Query candidate elements.matches(node, virtualNode?)
(fn): Further filter matches.all
,any
,none
(string[]): Arrays of check IDs.metadata
/tags
: Describe WCAG mapping and grouping.
Lifecycle:
- Collect:
document.querySelectorAll(selector)
- Filter: call
matches
(if defined), skip hidden by default. - Flatten: build a
VirtualNode
tree (see “Virtual DOM”). - Execute: run checks named in
all
/any
/none
. - Report: combine check statuses into rule result.
Check Definition
Checks encapsulate logic returning a standardized result:
// Example: lib/checks/has-alt-text.js
import { Result, results } from 'axe-core/lib/core/constants';
export function hasAltText({ node }) {
const alt = node.getAttribute('alt') || '';
return alt.trim()
? { status: results.PASS }
: { status: results.FAIL, message: 'Missing alt text' };
}
Key points:
- Receives a context object
{ node, virtualNode, options }
. - Returns a
CheckResult
:{ status, message?, data? }
. - Refer to constants for
status
values:PASS
,FAIL
,WARNING
,INCOMPLETE
.
Virtual DOM
Axe-core flattens real DOM (including Shadow DOM) into VirtualNode
instances:
interface VirtualNode {
el: Node; // original DOM node
parent: VirtualNode | null;
children: VirtualNode[];
shadowRootId?: string; // when entering Shadow DOM
// root node also has:
_selectorMap: Map<Node, string[]>;
_hasShadowRoot: boolean;
}
Use getFlattenedTree
to build it:
import getFlattenedTree from 'axe-core/lib/core/utils/get-flattened-tree';
const vnodes = getFlattenedTree(document.body);
console.log(vnodes[0]._hasShadowRoot);
Benefits:
- Uniform traversal across light and shadow DOM.
- Cached selector lookups in
_selectorMap
speed repeated rule evaluations.
Constants
Shared values ensure consistency across rules, checks, and reporting:
import {
results, // { PASS, FAIL, WARNING, INCOMPLETE }
impactValues, // ['critical','serious','moderate','minor']
statusDisplay, // maps statuses to UI colors/icons
url } // URL builder for help links
from 'axe-core/lib/core/constants';
console.log(results.PASS); // "passed"
console.log(impactValues); // ['critical','serious','moderate','minor']
Typical uses:
- Assign
metadata.impact = impactValues[1]
for “serious”. - Format result URLs:
url('image-alt')
.
Performance & Extension Tips
- Scope selectors narrowly to limit DOM queries.
- Reuse the root’s
_selectorMap
for custom lookups. - Cache flattened trees when testing static content.
- Place shared filter or check helpers under
lib/matches/
orlib/checks/
. - Leverage constants for status and impact to stay aligned with built-in rules.
Usage & Integration Guide
Everything a product-team engineer needs to customise axe-core for their stack: configure checks, target specific DOM contexts, and ensure compatibility across releases.
Customizing Accessibility Checks
Override default check options at runtime via axe.configure
. Pass a checks
array with { id, options }
entries matching each check’s metadata.
Key Points
id
: string identifier of the check (e.g."color-contrast"
)options
: shape matches the check’s JSON metadata underlib/checks/...
- Invalid
id
or malformedoptions
throws aTypeError
- Subsequent
axe.run
calls inherit your configuration
Examples
- Adjust color-contrast thresholds for large text only:
import axe from 'axe-core';
axe.configure({
checks: [
{
id: 'color-contrast',
options: {
contrastRatio: {
normal: { expected: 4.5 },
large: { expected: 3.0 } // warn large text if < 3.0
}
}
}
]
});
await axe.run(document);
- Permit additional language attributes in the
has-lang
check:
axe.configure({
checks: [
{
id: 'has-lang',
options: {
attributes: ['lang', 'xml:lang', 'hreflang']
}
}
]
});
- Combine multiple check overrides in one call:
axe.configure({
checks: [
{ id: 'color-contrast', options: { contrastRatio: { normal: { expected: 5.0 }, large: { expected: 3.0 } } } },
{ id: 'role-allowed-attr', options: { // custom role allowances
roleName: ['button', 'link'],
attributes: ['aria-pressed', 'aria-expanded']
}
}
],
rules: [
{ id: 'image-alt', enabled: false } // disable the image-alt rule
]
});
Targeting Specific Elements
Use the context
parameter in axe.run
to include/exclude nodes, shadow DOM hosts, or iframes.
1. Include & Exclude Selectors
// Only audit main landmarks, skip all buttons
await axe.run({
include: ['main', '[role="region"]'],
exclude: ['button']
});
2. Limiting Shadow DOM Testing
// Test <my-widget>’s internal form only
await axe.run({
fromShadowDom: ['my-widget', 'form.search']
});
// Nested shadow roots: root → header → search input
await axe.run({
fromShadowDom: ['app-root', '.header', '#search-input']
});
3. Combining Include/Exclude with Shadow DOM
await axe.run({
include: {
fromShadowDom: ['#shell', 'app-footer']
},
exclude: {
fromShadowDom: ['#shell', 'app-footer', '.ad-banner']
}
});
4. Testing Specific Frames
// Audit only the iframe at #help-frame
await axe.run({
fromFrames: ['#help-frame']
});
Practical Tips
- Attach custom elements to
document
before testing. - Slotted light-DOM children auto-include when their host is targeted.
- Don’t mix
fromShadowDom
directly ininclude
/exclude
arrays—wrap as shown. - For frame + shadow combos, use nested objects:
{ fromFrames: ['#f'], fromShadowDom: ['host', 'target'] }
Maintaining Backwards Compatibility
axe-core guarantees stability of its public API (functions, JSON structures, and utils) across patch and minor releases. Follow these practices:
- Use only public APIs:
axe.run
,axe.configure
,axe.reset
,axe.version
,axe.getRules
, etc. - Avoid internal utils under
axe.utils
and direct edits oflib/checks/*/*.json
. - Pin minor versions (
"axe-core": "^4.5.0"
) to prevent unexpected breaking changes. - Test custom rules after each minor upgrade; leverage CI to run
axe.run
against key pages/components. - Track deprecations in release notes. When an API pending removal appears, migrate before the next major.
Example: Guarding on axe.version
import axe from 'axe-core';
const [major] = axe.version.split('.');
if (parseInt(major, 10) < 4) {
console.warn('Upgrade to axe-core v4+ to use custom configurations');
}
// Safe to call configure and run
axe.configure({ /* ... */ });
await axe.run(document);
Following these guidelines ensures your custom rules and configurations continue working smoothly across axe-core releases.
API Reference
Cleanup API
Restore the page to its pre-audit state by running each plugin’s cleanup routine (including within iframes), then resolve or reject with any errors.
When to use
After you finish an axe.run()
and any custom plugin logic that modified the DOM or injected scripts/styles. Calling axe.cleanup()
ensures all plugins and frames revert their changes.
Key behaviors
- Invokes
plugin.cleanup(resolve, reject)
for each registered plugin - Sends
{ command: 'cleanup-plugin' }
to all<iframe>
/<frame>
nodes - Collects any cleanup errors and rejects with an array of errors
- Throws an Error if no audit is configured (
axe._audit
)
Signature
axe.cleanup(resolve?: Function, reject?: Function): Promise<any[]>
Parameters
resolve
(optional): Called with an array of results on successreject
(optional): Called with an array of Error instances on failure (defaults toaxe.log
)
Returns
A Promise that:
- resolves to an array of plugin/frame cleanup results
- rejects with an array of Error instances
Example usage
// Promise style
axe.run(document, { /* options */ })
.then(results => {
console.log('Audit results:', results);
return axe.cleanup();
})
.then(cleanupResults => {
console.log('All plugins cleaned up:', cleanupResults);
})
.catch(errs => {
console.error('Cleanup errors:', errs);
});
// Callback style
function onCleanupDone(results) {
console.log('Cleanup finished', results);
}
function onCleanupError(errors) {
console.warn('Some plugins failed to clean up', errors);
}
axe.cleanup(onCleanupDone, onCleanupError);
Practical tips
- Always call
axe.cleanup()
after modifying the DOM via plugins to prevent memory leaks. - For custom plugins, implement:
axe.plugins.myPlugin = { cleanup(resolve, reject) { try { document.body.classList.remove('myInjectedClass'); resolve('myPlugin cleaned'); } catch (e) { reject(e); } } };
- To clean up within frames, listen for
'message'
events in the frame and teardown on receiving{ command: 'cleanup-plugin' }
. - Handle multiple errors via
Array.isArray(errors)
.
DqElement: Element Serialization and Selectors
Wrap an HTMLElement
(or virtual node) to capture CSS selectors, ancestry, XPath, and serialized source for accessibility reporting. Configure how elements appear in results and extract human-readable identifiers.
Instantiation
new DqElement(element, options?, spec?)
options
defaults to settings fromDqElement.setRunOptions()
.spec
is used when merging frame contexts (fromFrame
).
Core getters
dq.selector
– array of one or more unique, readable CSS selectorsdq.ancestry
– array of ancestor selectors up to the rootdq.xpath
– array of XPath stringsdq.element
– originalHTMLElement
reference (if enabled)dq.source
– truncatedouterHTML
(unlessaxe._audit.noHtml
is true)
JSON serialization
JSON.stringify(dq)
invokes dq.toJSON()
and outputs:
selector
,ancestry
,xpath
,source
nodeIndexes
(flat-tree sort keys)fromFrame
booleanelement
ifelementRef
was enabled
Frame support
DqElement.fromFrame(childSpec, options?, parentFrameSpec)
Merges child and parent specs so selectors/xpaths reflect nested iframe contexts.
Global run-options
DqElement.setRunOptions({ elementRef: Boolean, absolutePaths: Boolean });
elementRef
: include raw DOM node in JSONabsolutePaths
: generate selectors from document root
Practical Usage
Configure default options:
import DqElement from 'axe-core/lib/core/utils/dq-element'; DqElement.setRunOptions({ elementRef: true, absolutePaths: true });
Wrap an element:
const dq = new DqElement(document.querySelector('button')); console.log(dq.selector[0]); // "#nav > .menu-item[data-active]" console.log(dq.ancestry); console.log(dq.xpath[0]); // "/html/body/div[2]/nav/ul/li[3]/button" console.log(dq.source); console.log(dq.element); // HTMLElement (if enabled)
Serialize for a JSON report:
const reportItem = { id: 'rule-1', nodes: [dq] }; const json = JSON.stringify(reportItem, null, 2); console.log(json);
Handling iframes:
// childSpec from axe’s child frame result // parentFrameSpec from parent DqElement.toJSON() const merged = DqElement.fromFrame(childSpec, null, parentFrame.toJSON()); console.log(merged.selector); // [ "iframe#checkout", ".item-button" ]
axe.run: Running Accessibility Analysis
Invoke axe-core to evaluate a page or sub-tree for accessibility issues. Supports Promise and callback styles, scoped scans, and customizable rule sets.
Signatures
// Promise-based
run<T = AxeResults>(context?: ElementContext): Promise<T>
run<T = AxeResults>(options: RunOptions): Promise<T>
run<T = AxeResults>(context: ElementContext, options: RunOptions): Promise<T>
// Callback-based
run<T = AxeResults>(callback: RunCallback<T>): void
run<T = AxeResults>(context: ElementContext, callback: RunCallback<T>): void
run<T = AxeResults>(options: RunOptions, callback: RunCallback<T>): void
run<T = AxeResults>(context: ElementContext, options: RunOptions, callback: RunCallback<T>): void
Key Types
- ElementContext: CSS selector string | DOM Node | NodeList | array of selectors |
{ include: ContextProp; exclude?: ContextProp }
- RunOptions (partial):
runOnly
: specify rules or tags to runrules
: enable/disable specific rulesresultTypes
: ['violations', 'passes', …]selectors
,xpath
,ancestry
: include metadata on failuresiframes
: include iframes in scan- timing:
frameWaitTime
,pingWaitTime
preload
:{ assets: string[]; timeout?: number }
Practical Examples
Full-page scan (Promise)
import axe from 'axe-core'; const results = await axe.run(); console.log('Violations:', results.violations);
Scan a region with include/exclude
const { violations, incomplete } = await axe.run( { include: '#main-content', exclude: ['.modal', '.tooltip'] }, { resultTypes: ['violations', 'incomplete'], selectors: true } );
Limit rules (callback)
axe.run( document.body, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] }, rules: { 'color-contrast': { enabled: false } } }, (err, results) => { if (err) throw err; console.table(results.passes, ['id', 'impact', 'help']); } );
Scanning Shadow DOM
const shadow = document.querySelector('#widget')!.shadowRoot!; const { violations } = await axe.run({ include: shadow.querySelectorAll('button') }); console.log(violations);
Best Practices
- Prefer Promise style for async/await flows.
- Limit scope with
include
/exclude
for performance. - Combine
runOnly
andrules
to target specific WCAG levels or components. - Enable
selectors
,xpath
, orancestry
for integration with reporting tools. - For multi-frame pages, set
iframes: true
and adjustframeWaitTime
if needed.
Advanced Topics
Deep-dive material for power users and maintainers of axe-core.
Performance Timer Utility
Shim the User Timing API to measure and log durations of axe-core audits and phases, with a graceful fallback.
Import
import performanceTimer from 'axe-core/lib/core/utils/performance-timer';
API Methods
- start()
Marksmark_axe_start
. - end()
Marksmark_axe_end
, measuresaxe
from start to end, then logs. - auditStart()
Marksmark_audit_start
. - auditEnd()
Marksmark_audit_end
, measuresaudit_start_to_end
, then logs all measures. - mark(name: string)
Creates or overwrites a timestamp. - measure(name: string, startMark: string, endMark: string)
Defines a measurement between two marks. - logMeasures(name?: string)
Logs all measures sincemark_axe_start
or only those matchingname
. - timeElapsed(): number
Returns ms since lastreset()
or module load. - reset()
Resets internal baseline fortimeElapsed()
.
Examples
Basic full-audit timing:
import performanceTimer from 'axe-core/lib/core/utils/performance-timer';
import axe from 'axe-core';
performanceTimer.start();
axe.run(document, {}, results => {
performanceTimer.end();
// Console: Measure axe took 123.45ms
console.log('Accessibility results:', results);
});
Per-frame audit timing:
performanceTimer.auditStart();
axe.run(frameDocument, {}, frameResults => {
performanceTimer.auditEnd();
// Console includes 'audit_start_to_end'
});
Custom phase timing:
performanceTimer.mark('phase1-begin');
// ...phase 1 logic...
performanceTimer.mark('phase1-end');
performanceTimer.measure('phase1', 'phase1-begin', 'phase1-end');
performanceTimer.logMeasures('phase1'); // logs only 'phase1'
One-off interval and reset:
const sinceLast = performanceTimer.timeElapsed();
console.log(`Time since last audit: ${sinceLast}ms`);
performanceTimer.reset();
Practical Guidance
- Ensure your environment supports the User Timing API; otherwise calls are no-ops.
- Wrap
axe.run
withstart()
/end()
to capture full audit duration. - Use
auditStart()
/auditEnd()
or custom marks for segmented audits. - Logs use axe’s log module (usually
console.log
), so you can hook or filter them. - Call
reset()
before a new session to keeptimeElapsed()
meaningful.
Creating and Updating Translation Files
Guide for generating new locale JSON files and synchronizing translations with the English source.
1. Generate or Refresh
Run the Grunt translate task with your language code:
grunt translate --lang=fr
This creates or overwrites locales/fr.json
from _template.json
.
2. JSON Structure
{
"lang": "fr",
"rules": {
"accesskeys": {
"description": "S’assurer que chaque valeur d’attribut accesskey est unique",
"help": "La valeur de l’attribut accesskey doit être unique"
}
},
"checks": {
"accesskeys": {
"pass": "Valeur d’accesskey unique",
"fail": "Le document contient plusieurs éléments avec la même accesskey"
}
}
}
- lang: language code
- rules: maps rule IDs to
description
andhelp
- checks: maps check IDs to
pass
/fail
/(optional)incomplete
, withsingular
/plural
variants
3. Translate
- Replace English text in all message fields.
- Preserve interpolation tokens (e.g.
${data.values}
). - Do not alter keys or nesting.
4. Keep Up to Date
Whenever English source changes:
grunt translate --lang=fr
- Merge new keys and translate them.
- Remove or flag obsolete entries.
5. Preview and Verify
- Run axe-core tests or integrate the locale in your build.
- Inspect reports to confirm correct rendering.
Reporting Security Issues
Approved process for reporting vulnerabilities in Deque-owned repositories. Do not open public issues; email reports to security@deque.com.
Report Contents
- Type of issue (e.g. XSS, SQL injection)
- Affected files (full paths)
- Location (branch/tag/commit hash or URL)
- Environment (OS, browser, library versions)
- Reproduction steps (detailed)
- Proof-of-concept or exploit code
- Impact assessment
Submission
Email to security@deque.com with subject:
[SECURITY] <Repository Name> – <Short Issue Description>
Email Template
To: security@deque.com
Subject: [SECURITY] axe-core – Cross-Site Scripting in CLI Reporter
Hello Deque Security Team,
1. Type of issue:
Cross-Site Scripting (CWE-79)
2. Affected files:
/packages/axe-core/cli/reporter.js
3. Location:
Tag v4.7.2, commit 9f3d2a1
https://github.com/dequelabs/axe-core/blob/v4.7.2/packages/cli/reporter.js#L120
4. Environment:
• Node.js v14.18.0
• Windows 10 Pro
5. Steps to reproduce:
a. Install axe-core CLI globally.
b. Run `axe https://example.com --reporter html --output report.html`.
c. Include `<script>alert('XSS')</script>` in data attributes.
d. Open report.html—alert appears.
6. Proof-of-concept:
Attached minimal HTML file `xss-demo.html`.
7. Impact:
Crafted HTML can execute arbitrary JS when viewing the report.
Please let me know if you need additional details.
Thank you,
Jane Developer
Tips
- Use
[SECURITY]
prefix to expedite triage. - Attach minimal repro code or logs.
- Provide clear, concise steps.
- Communicate in English for faster response.
Development Guide
This guide covers repository layout, coding standards, local development, testing, and the release process for axe-core contributors and maintainers.
Repository Layout
/ ← Project root
├─ .editorconfig ← Coding style rules
├─ CONTRIBUTING.md
├─ Gruntfile.js ← Build, test, and locale tasks
├─ package.json
├─ src/ ← Core modules and rules
├─ test/ ← Unit and integration tests
├─ locales/ ← Translation JSON files
└─ doc/
└─ code-submission-guidelines.md
Coding Standards
.editorconfig Snippet
Ensures consistent formatting across editors:
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,yml,yaml,md}]
trim_trailing_whitespace = false
Commit Message Format
Follow CONTRIBUTING.md
and code-submission-guidelines.md:
<type>(<scope>): <short description>
<body explaining motivation and changes>
• Types: feat, fix, chore, docs
• Use imperative mood and reference issue numbers when relevant.
Code Structure & Documentation
Return-early pattern:
function isValid(node) { if (!node) return false; // main logic } export default isValid;
Default export at file bottom
DocBlock comments for public functions:
/** * Evaluates whether `node` meets the foo rule. * @param {Element} node – DOM node to inspect. * @returns {boolean} */ function checkFoo(node) { … } export default checkFoo;
Development Setup & Build
Clone and install dependencies:
git clone https://github.com/dequelabs/axe-core.git cd axe-core npm ci npm run fmt # Formats code via Prettier
Common Grunt tasks:
Build core bundles:
grunt build
Run all tests:
grunt test
Generate a translation template:
grunt add-locale:template --lang xyz
Scaffold a new locale (e.g. French):
grunt add-locale:newLang --lang fr
Build localized bundles:
grunt build --lang fr
Testing
Write comprehensive tests—including Shadow DOM scenarios—and use the built-in fixtures utilities.
Run tests:
npm test npm test:locales # Validates locale JSON schema
Fixture utilities (
axe.testUtils
):import { getFixture, injectIntoFixture, fixtureSetup, queryFixture, checkSetup } from 'axe-core/testUtils'; // Inject HTML and run axe.setup const root = fixtureSetup(` <button id="target" aria-label="Click me"></button> `); // Prepare and invoke a single check const [node, opts, vNode] = checkSetup( '<button id="target" aria-label="Click"></button>', { someOption: true }, '#target' ); const result = checks['aria-button-name'].evaluate.call( new MockCheckContext(), node, opts, vNode, {} );
Ensure each new rule or feature includes unit tests in test/
, uses DocBlock comments, and follows the return-early pattern.
Release Process
Bump version in
package.json
and updateCHANGELOG.md
.Run a clean build:
npm ci grunt build
Commit and tag:
git add package.json CHANGELOG.md git commit -m "chore(release): vX.Y.Z" git tag vX.Y.Z git push origin main --tags
Publish to npm:
npm publish