Skip to content

Commit 41093da

Browse files
authored
chore(release): v4.11.2 (#5049)
### [4.11.2](v4.11.1...v4.11.2) (2026-03-30) ### Bug Fixes - **aria-valid-attr-value:** handle multiple aria-errormessage IDs ([#4973](#4973)) ([9322148](9322148)) - **aria:** prevent getOwnedVirtual from returning duplicate nodes ([#4987](#4987)) ([99d1e77](99d1e77)), closes [#4840](#4840) - **DqElement:** avoid calling constructors with cloneNode ([#5013](#5013)) ([88bc57f](88bc57f)) - **existing-rule:** aria-busy now shows an error message for a use with unallowed children ([#5017](#5017)) ([dded75a](dded75a)) - **scrollable-region-focusable:** clarify the issue is in safari ([#4995](#4995)) ([2567afd](2567afd)), closes [WebKit#190870](https://github.com/dequelabs/WebKit/issues/190870) [WebKit#277290](https://github.com/dequelabs/WebKit/issues/277290) - **scrollable-region-focusable:** do not fail scroll areas when all content is visible without scrolling ([#4993](#4993)) ([240f8b5](240f8b5)) - **target-size:** determine offset using clientRects if target is display:inline ([#5012](#5012)) ([69d81c1](69d81c1)) - **target-size:** ignore widgets that are inline with other inline elements ([#5000](#5000)) ([cf8a3c0](cf8a3c0))
2 parents fbd2f40 + 66c26aa commit 41093da

54 files changed

Lines changed: 2847 additions & 1489 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/install-deps/action.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ runs:
5656
id: setup-chrome
5757
uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0
5858
with:
59-
chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 'stable' }}
59+
# @see https://github.com/dequelabs/axe-core/issues/5027
60+
chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 145 }}
6061
install-chromedriver: true
6162
install-dependencies: true
6263
- name: Install Firefox

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ jobs:
173173
- name: Make Release Script Executable
174174
run: chmod +x ./node-github-release.sh
175175
- name: Create GitHub Release
176+
env:
177+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176178
run: ./node-github-release.sh
177179
validate-deploy:
178180
name: Validate Deployment

.github/workflows/update-generated-files.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Check for changes
3030
id: changes
3131
run: |
32-
changes=$(git status --porcelain)
32+
changes=$(git status --porcelain | tr -d '\n')
3333
# see https://unix.stackexchange.com/a/509498
3434
echo $changes | grep . && echo "Changes detected" || echo "No changes"
3535
echo "changes=$changes" >> "$GITHUB_OUTPUT"

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [4.11.2](https://github.com/dequelabs/axe-core/compare/v4.11.1...v4.11.2) (2026-03-30)
6+
7+
### Bug Fixes
8+
9+
- **aria-valid-attr-value:** handle multiple aria-errormessage IDs ([#4973](https://github.com/dequelabs/axe-core/issues/4973)) ([9322148](https://github.com/dequelabs/axe-core/commit/9322148b69924d6ae2d2dd46e6109bc2fc53abd3))
10+
- **aria:** prevent getOwnedVirtual from returning duplicate nodes ([#4987](https://github.com/dequelabs/axe-core/issues/4987)) ([99d1e77](https://github.com/dequelabs/axe-core/commit/99d1e77f351db586c79020372efc59608e604a1c)), closes [#4840](https://github.com/dequelabs/axe-core/issues/4840)
11+
- **DqElement:** avoid calling constructors with cloneNode ([#5013](https://github.com/dequelabs/axe-core/issues/5013)) ([88bc57f](https://github.com/dequelabs/axe-core/commit/88bc57fd3de12cd69d365b3f385ce9a2e30b7bd5))
12+
- **existing-rule:** aria-busy now shows an error message for a use with unallowed children ([#5017](https://github.com/dequelabs/axe-core/issues/5017)) ([dded75a](https://github.com/dequelabs/axe-core/commit/dded75a9acc9a2350926f46f04e0f1de522f43d6))
13+
- **scrollable-region-focusable:** clarify the issue is in safari ([#4995](https://github.com/dequelabs/axe-core/issues/4995)) ([2567afd](https://github.com/dequelabs/axe-core/commit/2567afd5c32398c6a488240b066bb0d335f6dc6a)), closes [WebKit#190870](https://github.com/dequelabs/WebKit/issues/190870) [WebKit#277290](https://github.com/dequelabs/WebKit/issues/277290)
14+
- **scrollable-region-focusable:** do not fail scroll areas when all content is visible without scrolling ([#4993](https://github.com/dequelabs/axe-core/issues/4993)) ([240f8b5](https://github.com/dequelabs/axe-core/commit/240f8b53ad168521a63b54d0053b96ce430c7184))
15+
- **target-size:** determine offset using clientRects if target is display:inline ([#5012](https://github.com/dequelabs/axe-core/issues/5012)) ([69d81c1](https://github.com/dequelabs/axe-core/commit/69d81c1cbb6a61a272884516c7983dcd17d28a42))
16+
- **target-size:** ignore widgets that are inline with other inline elements ([#5000](https://github.com/dequelabs/axe-core/issues/5000)) ([cf8a3c0](https://github.com/dequelabs/axe-core/commit/cf8a3c039b121c0c64a1eb8805c586659b69f3c1))
17+
518
### [4.11.1](https://github.com/dequelabs/axe-core/compare/v4.11.0...v4.11.1) (2026-01-06)
619

720
### Bug Fixes

CLAUDE.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# axe-core — Claude Code Context
2+
3+
**Last updated:** 2026-03-19
4+
5+
## 0. Fundamental Standards
6+
7+
- **Formatting:** Run `npm run fmt` (Prettier) and `npm run eslint` before every commit. No exceptions.
8+
- **Zero-Exception Testing:** 100% coverage goal. Run `npm test` before completion. `feat`/`fix` and other behavior-changing PRs require unit + integration tests where applicable; see `doc/code-submission-guidelines.md` for exceptions (e.g., some `chore`/docs changes).
9+
- **Import Rule:** Directory-level import restrictions are strictly enforced. See `eslint.config.js`.
10+
- **Commits:** Angular commit convention is mandatory. PRs with non-conforming commits will be rejected. See `doc/code-submission-guidelines.md`.
11+
- **Issues:** All unresolved issues tracked in [GitHub Issues](https://github.com/dequelabs/axe-core/issues).
12+
13+
## 1. Technical Guidelines
14+
15+
### Code Structure
16+
17+
- **Return Early:** Keep the happy path left-aligned. Handle errors/edge cases first with early returns — never nest when you can exit.
18+
- **Exports:** Default export at the top of the file, immediately after imports.
19+
- **JSDoc:** Add JSDoc/DocBlock comments where appropriate, especially for exported APIs and complex logic. Document parameters and return types. See `doc/code-submission-guidelines.md` (source of truth) and `doc/developer-guide.md` for additional guidance.
20+
- **Naming:** Files and rule/check IDs in kebab-case. Functions in camelCase. Booleans prefixed `is`/`has`/`should`. Constants in `UPPER_SNAKE_CASE`.
21+
- **Variables:** Declare at point of use, not at the top of the function.
22+
23+
### Virtual Nodes vs. HTMLElement
24+
25+
- **Prefer Virtual Nodes** for attribute access and property reads (`virtualNode.attr()`, `virtualNode.props`).
26+
- **Use real DOM** only when you need DOM APIs (e.g., `getBoundingClientRect`, `getRootNode`).
27+
- **`core/utils/` functions** should default to real DOM inputs/outputs, except utilities that are explicitly documented as operating on `VirtualNode` (for example, tree or selector helpers). Avoid introducing new `VirtualNode`-dependent utilities unless there is a clear performance or API benefit.
28+
- **Conversion:** Use `getNodeFromTree()` from `core/utils` when you receive an ambiguous input (such as a `VirtualNode`, selector, or mixed type) and need to resolve it to a real DOM `Node`.
29+
30+
### Import Restrictions (Hard Rules)
31+
32+
- `standards/` → nothing (pure data, no imports).
33+
- `core/utils/` → other `core/utils`, `core/base`, `standards` via **direct file paths only** (no index).
34+
- `core/imports/` → node modules **only** (the only place npm imports are allowed).
35+
- `commons/` → other `commons` (direct paths), `core/utils` (index OK).
36+
- `checks/` and `rules/` → any directory (index OK).
37+
- **Never** import `commons` from `core/utils`. This is the most commonly rejected violation.
38+
39+
### Checks & Rules
40+
41+
- **Check evaluate functions:** Return `true` (pass), `false` (fail), or `undefined` (incomplete). Use `this.data()` to pass values to message templates and to provide incomplete-result detail.
42+
- **Rule JSON:** Use `all`, `any`, `none` check arrays. `selector` + optional `matches` to scope candidates. Valid `impact` values: `"minor"`, `"moderate"`, `"serious"`, `"critical"`.
43+
- **Standards data:** Never hardcode ARIA/HTML lists in checks. Query from `standards/` via `commons/standards` functions.
44+
- **Messages:** All user-facing strings live in check/rule JSON `metadata.messages`. Use `${data.property}` templates. Support singular/plural variants. Update `locales/_template.json` whenever messages change (auto-synced on `npm run build`).
45+
46+
### High-Risk Areas (Extra Scrutiny Required)
47+
48+
- **Color contrast:** Handles all CSS color formats, opacity, blend modes, stacking contexts, text-shadow. Return `undefined` when background cannot be determined. See `doc/developer-guide.md`.
49+
- **ARIA validation:** Must stay current with spec. Query roles/attrs from `standards/aria-roles.js` and `standards/aria-attrs.js`. Handle implicit vs. explicit roles. See `doc/rule-development.md`.
50+
- **Hidden elements:** Use `isVisibleToScreenReaders()`, not CSS visibility alone. Account for `aria-hidden="true"` and Shadow DOM boundaries.
51+
- **i18n:** Update `locales/_template.json` on every message change and commit the generated file alongside source.
52+
53+
## 2. Testing
54+
55+
- **Structure:** Mirror `lib/` exactly under `test/`. File `lib/commons/text/sanitize.js``test/commons/text/sanitize.js`.
56+
- **Checks:** Use `axe.testUtils.MockCheckContext()`. Only reset `checkContext` in `afterEach``fixture` and `axe._tree` are auto-cleared.
57+
- **Integration tests:** All rule changes require an HTML + JSON pair. Use `test/integration/rules/<rule-name>/` for mocha-hosted tests or `test/integration/full/<rule-name>/` when the rule requires a full HTML page. JSON selectors must use axe array format (`["#id"]`; iframes: `["iframe", "#id"]`). Also update or create virtual-rules tests where appropriate.
58+
- **Shadow DOM:** Every relevant check/rule must include an open Shadow DOM test case using `queryShadowFixture`.
59+
- **Logging:** Do not commit `console.log` statements.
60+
61+
## 3. Build & Commits
62+
63+
- **Bundles (`axe.js`, `axe.min.js`)** are auto-generated for releases/publishing and are not committed to the repo (they are gitignored).
64+
- **Locales template (`locales/_template.json`)** is auto-generated. When message strings change, regenerate this file and commit it in the same commit as the source changes — never in a separate commit.
65+
- **One change per PR.** Do not mix refactoring with feature work.
66+
- **Commit format:** `<type>(<scope>): <subject>` — imperative, lowercase, no period, ≤100 chars total. Body explains motivation. Footer: `Closes issue #123` or full URL. See `doc/code-submission-guidelines.md` for the full type list.
67+
68+
**Example:**
69+
70+
```
71+
fix(aria-valid-attr-value): handle multiple aria-errormessage IDs
72+
73+
When aria-errormessage contains multiple space-separated IDs, verify
74+
all IDs exist in aria-describedby instead of matching the full string.
75+
76+
Closes issue #4957
77+
```
78+
79+
## 4. Documentation & API Changes
80+
81+
- **New rules:** Update `CHANGELOG.md`. `doc/rule-descriptions.md` is auto-generated by `npm run build`.
82+
- **API changes:** Update `doc/API.md` and `axe.d.ts` TypeScript definitions.
83+
- **Breaking changes:** Avoid breaking changes — prefer supporting both old and new formats simultaneously. If unavoidable, add `BREAKING CHANGE: description` to commit footer, include migration guide in `CHANGELOG.md`, and tag deprecated code with `@deprecated` JSDoc.
84+
85+
## 5. Examples (Copy-Paste Reference)
86+
87+
- **Code patterns:** `doc/examples/code-patterns.md` — return early, default export, imports, JSDoc, Virtual Node usage
88+
- **Test patterns:** `doc/examples/test-patterns.md` — unit tests, check tests, Shadow DOM tests, integration test HTML+JSON
89+
- **Rule & check templates:** `doc/examples/rule-check-templates.md` — JSON templates for rules and checks, evaluate function pattern
90+
- **PR review patterns:** `doc/examples/pr-review-patterns.md` — common reviewer feedback, anti-patterns, what reviewers look for
91+
92+
## 6. Reference Docs & Help
93+
94+
- **Contributing guide:** `CONTRIBUTING.md`
95+
- **Import rules detail:** `eslint.config.js`
96+
- **Code submission standards:** `doc/code-submission-guidelines.md`
97+
- **Developer guide:** `doc/developer-guide.md`
98+
- **Rule development:** `doc/rule-development.md`
99+
- **API reference:** `doc/API.md`
100+
- **Pull Request Checklist:** `doc/pull-request-checklist.md`
101+
- **Slack:** [axe-community](https://accessibility.deque.com/axe-community)
102+
- **Issues:** [GitHub Issues](https://github.com/dequelabs/axe-core/issues)

axe.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ declare namespace axe {
467467
}
468468

469469
interface Utils {
470+
getElementSource: (
471+
element: Node | null | undefined,
472+
options?: { maxLength?: number; attrLimit?: number }
473+
) => string;
470474
getFrameContexts: (
471475
context?: ElementContext,
472476
options?: RunOptions

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "axe-core",
3-
"version": "4.11.1",
3+
"version": "4.11.2",
44
"deprecated": true,
55
"contributors": [
66
{

doc/examples/code-patterns.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Code Pattern Examples
2+
3+
Quick-reference examples for axe-core coding conventions.
4+
5+
## Default Export at Top
6+
7+
```javascript
8+
// GOOD: Default export right after imports
9+
import { getRole } from '../../commons/aria';
10+
import { isVisible } from '../../commons/dom';
11+
12+
export default function myFunction(node, options) {
13+
// function body
14+
}
15+
16+
// BAD: Export buried at bottom of file
17+
import { getRole } from '../../commons/aria';
18+
19+
function myFunction(node, options) {
20+
// body
21+
}
22+
23+
// ... more code ...
24+
25+
export default myFunction; // Too far from top
26+
```
27+
28+
## Return Early Pattern
29+
30+
```javascript
31+
// GOOD: Main path left-aligned, edge cases exit early
32+
export default function processValue(value) {
33+
if (!value) {
34+
return null;
35+
}
36+
37+
if (value.length < 3) {
38+
throw new Error('Value too short');
39+
}
40+
41+
const normalized = normalize(value);
42+
const result = transform(normalized);
43+
return result;
44+
}
45+
46+
// BAD: Nested conditionals
47+
export default function processValue(value) {
48+
let result;
49+
if (value) {
50+
if (value.length >= 3) {
51+
const normalized = normalize(value);
52+
result = transform(normalized);
53+
} else {
54+
throw new Error('Value too short');
55+
}
56+
} else {
57+
result = null;
58+
}
59+
return result;
60+
}
61+
```
62+
63+
## Import Restrictions
64+
65+
```javascript
66+
// GOOD: commons importing from core/utils via index
67+
import { getNodeFromTree } from '../../core/utils';
68+
69+
// GOOD: commons importing other commons directly
70+
import getExplicitRole from '../aria/get-explicit-role';
71+
72+
// BAD: core/utils importing from commons — NEVER DO THIS
73+
import { isDisabled } from '../../commons/forms';
74+
75+
// BAD: importing from index in core/utils — use direct path
76+
import { someUtil } from './index'; // Use: import someUtil from './some-util';
77+
```
78+
79+
## JSDoc Comments
80+
81+
### Standard function
82+
83+
```javascript
84+
/**
85+
* Determines if an element is a native select element
86+
* @method isNativeSelect
87+
* @memberof axe.commons.forms
88+
* @param {VirtualNode|Element} node Node to determine if select
89+
* @returns {Boolean}
90+
*/
91+
import nodeLookup from '../../core/utils/node-lookup';
92+
93+
function isNativeSelect(node) {
94+
const { vNode } = nodeLookup(node);
95+
const nodeName = vNode.props.nodeName;
96+
return nodeName === 'select';
97+
}
98+
```
99+
100+
### Check evaluate function
101+
102+
```javascript
103+
/**
104+
* Check if an element's `role` attribute uses any abstract role values.
105+
*
106+
* Abstract roles are taken from the `ariaRoles` standards object from the roles `type` property.
107+
*
108+
* ##### Data:
109+
* <table class="props">
110+
* <thead>
111+
* <tr>
112+
* <th>Type</th>
113+
* <th>Description</th>
114+
* </tr>
115+
* </thead>
116+
* <tbody>
117+
* <tr>
118+
* <td><code>String[]</code></td>
119+
* <td>List of all abstract roles</td>
120+
* </tr>
121+
* </tbody>
122+
* </table>
123+
*
124+
* @memberof checks
125+
* @return {Boolean} True if the element uses an `abstract` role. False otherwise.
126+
*/
127+
function abstractroleEvaluate(node, options, virtualNode) {
128+
// implementation
129+
}
130+
```
131+
132+
## Virtual Node vs HTMLElement
133+
134+
```javascript
135+
// Use Virtual Node for attribute access and property reads
136+
function myCheck(node, options, virtualNode) {
137+
const role = virtualNode.attr('role'); // Cached attribute access
138+
const nodeName = virtualNode.props.nodeName;
139+
140+
// Only access real node when you need DOM APIs
141+
const rect = node.getBoundingClientRect();
142+
const rootNode = node.getRootNode();
143+
}
144+
145+
// Convert ambiguous input using nodeLookup
146+
import nodeLookup from '../../core/utils/node-lookup';
147+
148+
function myFunction(nodeOrVirtual) {
149+
const { vNode, domNode } = nodeLookup(nodeOrVirtual);
150+
// vNode = VirtualNode, domNode = real DOM node
151+
}
152+
```

doc/examples/pr-review-patterns.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# PR Review Patterns
2+
3+
Common feedback and anti-patterns observed in axe-core code reviews.
4+
5+
## What Gets Called Out
6+
7+
### 1. Missing Tests
8+
9+
- Every behavior-changing code change needs unit tests
10+
- Rule changes need integration tests (HTML + JSON pair)
11+
- Shadow DOM test coverage required for relevant checks/rules
12+
13+
### 2. Commit Message Format
14+
15+
- Wrong type/scope
16+
- Not imperative present tense
17+
- Subject too long or capitalized
18+
- Missing issue reference in footer
19+
20+
### 3. Import Violations
21+
22+
- `core/utils` importing from `commons` (forbidden)
23+
- Using index imports where direct file paths are required
24+
- Importing node modules outside `core/imports`
25+
26+
### 4. Code Style Issues
27+
28+
- Not using return early pattern
29+
- Default export not at top of file
30+
- Nested conditionals when early return would work
31+
- Missing JSDoc comments
32+
33+
### 5. Performance Concerns
34+
35+
- Unnecessary DOM queries in loops
36+
- Not caching Virtual Node properties
37+
- Computing same value repeatedly instead of storing it
38+
39+
### 6. Incomplete Results
40+
41+
- Not returning `undefined` when a check can't determine the result
42+
- Missing `incomplete` message variants in check JSON
43+
- Not setting appropriate `this.data()` for incomplete cases
44+
45+
### 7. Accessibility Edge Cases
46+
47+
- Not considering all ARIA states
48+
- Missing edge cases for hidden elements
49+
- Not handling Shadow DOM properly
50+
51+
## What Reviewers Love
52+
53+
1. Comprehensive test coverage including edge cases
54+
2. Clear, detailed commit messages with motivation
55+
3. JSDoc comments that explain "why" not just "what"
56+
4. Performance-conscious code that caches appropriately
57+
5. Following existing patterns in similar files
58+
6. Integration tests that cover both pass and fail cases
59+
60+
## Common PR Mistakes
61+
62+
1. Not running `npm test` locally before pushing
63+
2. Not committing auto-generated `locales/_template.json` in the same commit as message source changes
64+
3. Changing multiple unrelated things in one PR — split refactoring from feature work
65+
4. Not updating integration tests when changing rule behavior
66+
5. `console.log` statements should not be committed
67+
6. Not handling `null` or `undefined` gracefully
68+
7. Hardcoding strings that should come from `standards/` data
69+
8. Changing public APIs without `BREAKING CHANGE` in commit footer

0 commit comments

Comments
 (0)