Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2356501
ci: fix release script (#4984)
straker Jan 6, 2026
2249a05
test: fix test:unit (#4989)
straker Jan 13, 2026
9322148
fix(aria-valid-attr-value): handle multiple aria-errormessage IDs (#4…
JustasMonkev Jan 26, 2026
2567afd
fix(scrollable-region-focusable): clarify the issue is in safari (#4995)
WilcoFiers Jan 29, 2026
6d19496
chore: sync generated files (#5007)
straker Feb 3, 2026
97f1190
ci: fix update-generated-files workflow (#5006)
straker Feb 3, 2026
5be3a2b
chore: bump globals from 16.5.0 to 17.1.0 (#5005)
dependabot[bot] Feb 3, 2026
818e197
chore: bump jquery from 3.7.1 to 4.0.0 (#5004)
dependabot[bot] Feb 3, 2026
240f8b5
fix(scrollable-region-focusable): do not fail scroll areas when all c…
straker Feb 5, 2026
91b2c28
chore: bump the npm-low-risk group across 1 directory with 8 updates …
dependabot[bot] Feb 5, 2026
88bc57f
fix(DqElement): avoid calling constructors with cloneNode (#5013)
WilcoFiers Feb 18, 2026
99d1e77
fix(aria): prevent getOwnedVirtual from returning duplicate nodes (#4…
camiha Feb 18, 2026
69d81c1
fix(target-size): determine offset using clientRects if target is dis…
straker Feb 20, 2026
dded75a
fix(existing-rule): aria-busy now shows an error message for a use wi…
chutchins25 Mar 4, 2026
68aab66
test: fix chromedriver 146 failing to create session (#5026)
straker Mar 7, 2026
431f621
chore: bump jsdom from 27.4.0 to 28.1.0 (#5021)
dependabot[bot] Mar 13, 2026
a09204f
chore: bump the npm-low-risk group with 6 updates (#5020)
dependabot[bot] Mar 13, 2026
3d80a37
chore: add CLAUDE.md and pull request checklist (#5035)
dylanb Mar 19, 2026
cf8a3c0
fix(target-size): ignore widgets that are inline with other inline el…
straker Mar 26, 2026
66c26aa
chore(release): 4.11.2
straker Mar 30, 2026
41093da
chore(release): v4.11.2 (#5049)
WilcoFiers Mar 31, 2026
7e06043
chore: bump actions/download-artifact from 7.0.0 to 8.0.1 (#5056)
dependabot[bot] Apr 1, 2026
0463a3f
chore: bump actions/upload-artifact from 6.0.0 to 7.0.0 (#5054)
dependabot[bot] Apr 1, 2026
baa580b
chore: bump the npm-low-risk group with 8 updates (#5053)
dependabot[bot] Apr 1, 2026
1d80163
fix(aria-allowed-attr): restrict br and wbr elements to aria-hidden o…
nami8824 Apr 7, 2026
d5a5705
refactor(frame-messenger): Guard against inherited properties as topi…
RinZ27 Apr 9, 2026
5906273
fix(target-size): ignore position: fixed elements that are offscreen …
straker Apr 10, 2026
3ab66ba
chore(release): 4.11.3
straker Apr 13, 2026
c71e3dd
chore(release): v4.11.3 (#5070)
WilcoFiers Apr 13, 2026
6e68d0a
fix(utils/getAncestry): escape node name (#5079)
straker Apr 22, 2026
fb85080
chore: fix cherry-pick script buffer size error for large git logs (#…
straker Apr 23, 2026
df34adf
fix(commons/text): exclude natively hidden elements from aria-labelle…
nami8824 Apr 23, 2026
cea72d3
chore(release): 4.11.4
straker Apr 23, 2026
be1a0ab
fix(sri-history): correct axe.js hash for 4.11.4
michael-siek Apr 28, 2026
dfbc245
chore: Release 4.11.4 (#5081)
WilcoFiers Apr 28, 2026
d76303c
Merge tag 'v4.11.4' into AXE-3659-R2-axe-4.11.4
rohitsahu-bstack Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/actions/install-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ runs:
id: setup-chrome
uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0
with:
chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 'stable' }}
# @see https://github.com/dequelabs/axe-core/issues/5027
chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 145 }}
install-chromedriver: true
install-dependencies: true
- name: Install Firefox
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ jobs:
- name: Make Release Script Executable
run: chmod +x ./node-github-release.sh
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./node-github-release.sh
validate-deploy:
name: Validate Deployment
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-generated-files.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Check for changes
id: changes
run: |
changes=$(git status --porcelain)
changes=$(git status --porcelain | tr -d '\n')
# see https://unix.stackexchange.com/a/509498
echo $changes | grep . && echo "Changes detected" || echo "No changes"
echo "changes=$changes" >> "$GITHUB_OUTPUT"
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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.

### [4.11.4](https://github.com/dequelabs/axe-core/compare/v4.11.3...v4.11.4) (2026-04-23)

### Bug Fixes

- **commons/text:** exclude natively hidden elements from aria-labelledby accessible name ([#5076](https://github.com/dequelabs/axe-core/issues/5076)) ([df34adf](https://github.com/dequelabs/axe-core/commit/df34adfc1967919d667d40a76ab5c85b6e47ddfe)), closes [#4704](https://github.com/dequelabs/axe-core/issues/4704)
- **utils/getAncestry:** escape node name ([#5079](https://github.com/dequelabs/axe-core/issues/5079)) ([6e68d0a](https://github.com/dequelabs/axe-core/commit/6e68d0a5d26999b996152df82238bc3f3a041cb3)), closes [#5078](https://github.com/dequelabs/axe-core/issues/5078)

### [4.11.3](https://github.com/dequelabs/axe-core/compare/v4.11.2...v4.11.3) (2026-04-13)

### Bug Fixes

- **aria-allowed-attr:** restrict br and wbr elements to aria-hidden only ([#4974](https://github.com/dequelabs/axe-core/issues/4974)) ([1d80163](https://github.com/dequelabs/axe-core/commit/1d801636f058f2abd885c488baff954872b13846))
- **target-size:** ignore position: fixed elements that are offscreen when page is scrolled ([#5066](https://github.com/dequelabs/axe-core/issues/5066)) ([5906273](https://github.com/dequelabs/axe-core/commit/5906273841cbd7ac9e08af730dffc244cf42b39b)), closes [#5065](https://github.com/dequelabs/axe-core/issues/5065)

### [4.11.2](https://github.com/dequelabs/axe-core/compare/v4.11.1...v4.11.2) (2026-03-30)

### Bug Fixes

- **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))
- **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)
- **DqElement:** avoid calling constructors with cloneNode ([#5013](https://github.com/dequelabs/axe-core/issues/5013)) ([88bc57f](https://github.com/dequelabs/axe-core/commit/88bc57fd3de12cd69d365b3f385ce9a2e30b7bd5))
- **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))
- **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)
- **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))
- **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))
- **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))

### [4.11.1](https://github.com/dequelabs/axe-core/compare/v4.11.0...v4.11.1) (2026-01-06)

### Bug Fixes
Expand Down
102 changes: 102 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# axe-core — Claude Code Context

**Last updated:** 2026-03-19

## 0. Fundamental Standards

- **Formatting:** Run `npm run fmt` (Prettier) and `npm run eslint` before every commit. No exceptions.
- **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).
- **Import Rule:** Directory-level import restrictions are strictly enforced. See `eslint.config.js`.
- **Commits:** Angular commit convention is mandatory. PRs with non-conforming commits will be rejected. See `doc/code-submission-guidelines.md`.
- **Issues:** All unresolved issues tracked in [GitHub Issues](https://github.com/dequelabs/axe-core/issues).

## 1. Technical Guidelines

### Code Structure

- **Return Early:** Keep the happy path left-aligned. Handle errors/edge cases first with early returns — never nest when you can exit.
- **Exports:** Default export at the top of the file, immediately after imports.
- **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.
- **Naming:** Files and rule/check IDs in kebab-case. Functions in camelCase. Booleans prefixed `is`/`has`/`should`. Constants in `UPPER_SNAKE_CASE`.
- **Variables:** Declare at point of use, not at the top of the function.

### Virtual Nodes vs. HTMLElement

- **Prefer Virtual Nodes** for attribute access and property reads (`virtualNode.attr()`, `virtualNode.props`).
- **Use real DOM** only when you need DOM APIs (e.g., `getBoundingClientRect`, `getRootNode`).
- **`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.
- **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`.

### Import Restrictions (Hard Rules)

- `standards/` → nothing (pure data, no imports).
- `core/utils/` → other `core/utils`, `core/base`, `standards` via **direct file paths only** (no index).
- `core/imports/` → node modules **only** (the only place npm imports are allowed).
- `commons/` → other `commons` (direct paths), `core/utils` (index OK).
- `checks/` and `rules/` → any directory (index OK).
- **Never** import `commons` from `core/utils`. This is the most commonly rejected violation.

### Checks & Rules

- **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.
- **Rule JSON:** Use `all`, `any`, `none` check arrays. `selector` + optional `matches` to scope candidates. Valid `impact` values: `"minor"`, `"moderate"`, `"serious"`, `"critical"`.
- **Standards data:** Never hardcode ARIA/HTML lists in checks. Query from `standards/` via `commons/standards` functions.
- **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`).

### High-Risk Areas (Extra Scrutiny Required)

- **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`.
- **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`.
- **Hidden elements:** Use `isVisibleToScreenReaders()`, not CSS visibility alone. Account for `aria-hidden="true"` and Shadow DOM boundaries.
- **i18n:** Update `locales/_template.json` on every message change and commit the generated file alongside source.

## 2. Testing

- **Structure:** Mirror `lib/` exactly under `test/`. File `lib/commons/text/sanitize.js` → `test/commons/text/sanitize.js`.
- **Checks:** Use `axe.testUtils.MockCheckContext()`. Only reset `checkContext` in `afterEach` — `fixture` and `axe._tree` are auto-cleared.
- **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.
- **Shadow DOM:** Every relevant check/rule must include an open Shadow DOM test case using `queryShadowFixture`.
- **Logging:** Do not commit `console.log` statements.

## 3. Build & Commits

- **Bundles (`axe.js`, `axe.min.js`)** are auto-generated for releases/publishing and are not committed to the repo (they are gitignored).
- **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.
- **One change per PR.** Do not mix refactoring with feature work.
- **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.

**Example:**

```
fix(aria-valid-attr-value): handle multiple aria-errormessage IDs

When aria-errormessage contains multiple space-separated IDs, verify
all IDs exist in aria-describedby instead of matching the full string.

Closes issue #4957
```

## 4. Documentation & API Changes

- **New rules:** Update `CHANGELOG.md`. `doc/rule-descriptions.md` is auto-generated by `npm run build`.
- **API changes:** Update `doc/API.md` and `axe.d.ts` TypeScript definitions.
- **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.

## 5. Examples (Copy-Paste Reference)

- **Code patterns:** `doc/examples/code-patterns.md` — return early, default export, imports, JSDoc, Virtual Node usage
- **Test patterns:** `doc/examples/test-patterns.md` — unit tests, check tests, Shadow DOM tests, integration test HTML+JSON
- **Rule & check templates:** `doc/examples/rule-check-templates.md` — JSON templates for rules and checks, evaluate function pattern
- **PR review patterns:** `doc/examples/pr-review-patterns.md` — common reviewer feedback, anti-patterns, what reviewers look for

## 6. Reference Docs & Help

- **Contributing guide:** `CONTRIBUTING.md`
- **Import rules detail:** `eslint.config.js`
- **Code submission standards:** `doc/code-submission-guidelines.md`
- **Developer guide:** `doc/developer-guide.md`
- **Rule development:** `doc/rule-development.md`
- **API reference:** `doc/API.md`
- **Pull Request Checklist:** `doc/pull-request-checklist.md`
- **Slack:** [axe-community](https://accessibility.deque.com/axe-community)
- **Issues:** [GitHub Issues](https://github.com/dequelabs/axe-core/issues)
4 changes: 4 additions & 0 deletions axe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ declare namespace axe {
}

interface Utils {
getElementSource: (
element: Node | null | undefined,
options?: { maxLength?: number; attrLimit?: number }
) => string;
getFrameContexts: (
context?: ElementContext,
options?: RunOptions
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axe-core",
"version": "4.11.1",
"version": "4.11.4",
"deprecated": true,
"contributors": [
{
Expand Down
6 changes: 5 additions & 1 deletion build/cherry-pick.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const conventionalCommitsParser = require('conventional-commits-parser');
const chalk = require('chalk');
const { version } = require('../package.json');

// Node's default execSync buffer (~1 MiB) is too small for `git log` on long histories.
const GIT_LOG_EXEC_OPTS = { maxBuffer: 50 * 1024 * 1024 };

const releaseType = process.argv[2];
let ignoreCommits = process.argv[3];

Expand Down Expand Up @@ -50,7 +53,8 @@ function getCommits(branch) {
// all commits are too large for execSync buffer size so we'll just get since the last 3 years
const date = new Date(new Date().setFullYear(new Date().getFullYear() - 3));
const stdout = execSync(
`git log ${branch || ''} --abbrev-commit --since=${date.getFullYear()}`
`git log ${branch || ''} --no-merges --abbrev-commit --since=${date.getFullYear()}`,
GIT_LOG_EXEC_OPTS
).toString();
const allCommits = stdout
.split(/commit (?=[\w\d]{8}[\n\r])/)
Expand Down
152 changes: 152 additions & 0 deletions doc/examples/code-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Code Pattern Examples

Quick-reference examples for axe-core coding conventions.

## Default Export at Top

```javascript
// GOOD: Default export right after imports
import { getRole } from '../../commons/aria';
import { isVisible } from '../../commons/dom';

export default function myFunction(node, options) {
// function body
}

// BAD: Export buried at bottom of file
import { getRole } from '../../commons/aria';

function myFunction(node, options) {
// body
}

// ... more code ...

export default myFunction; // Too far from top
```

## Return Early Pattern

```javascript
// GOOD: Main path left-aligned, edge cases exit early
export default function processValue(value) {
if (!value) {
return null;
}

if (value.length < 3) {
throw new Error('Value too short');
}

const normalized = normalize(value);
const result = transform(normalized);
return result;
}

// BAD: Nested conditionals
export default function processValue(value) {
let result;
if (value) {
if (value.length >= 3) {
const normalized = normalize(value);
result = transform(normalized);
} else {
throw new Error('Value too short');
}
} else {
result = null;
}
return result;
}
```

## Import Restrictions

```javascript
// GOOD: commons importing from core/utils via index
import { getNodeFromTree } from '../../core/utils';

// GOOD: commons importing other commons directly
import getExplicitRole from '../aria/get-explicit-role';

// BAD: core/utils importing from commons — NEVER DO THIS
import { isDisabled } from '../../commons/forms';

// BAD: importing from index in core/utils — use direct path
import { someUtil } from './index'; // Use: import someUtil from './some-util';
```

## JSDoc Comments

### Standard function

```javascript
/**
* Determines if an element is a native select element
* @method isNativeSelect
* @memberof axe.commons.forms
* @param {VirtualNode|Element} node Node to determine if select
* @returns {Boolean}
*/
import nodeLookup from '../../core/utils/node-lookup';

function isNativeSelect(node) {
const { vNode } = nodeLookup(node);
const nodeName = vNode.props.nodeName;
return nodeName === 'select';
}
```

### Check evaluate function

```javascript
/**
* Check if an element's `role` attribute uses any abstract role values.
*
* Abstract roles are taken from the `ariaRoles` standards object from the roles `type` property.
*
* ##### Data:
* <table class="props">
* <thead>
* <tr>
* <th>Type</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><code>String[]</code></td>
* <td>List of all abstract roles</td>
* </tr>
* </tbody>
* </table>
*
* @memberof checks
* @return {Boolean} True if the element uses an `abstract` role. False otherwise.
*/
function abstractroleEvaluate(node, options, virtualNode) {
// implementation
}
```

## Virtual Node vs HTMLElement

```javascript
// Use Virtual Node for attribute access and property reads
function myCheck(node, options, virtualNode) {
const role = virtualNode.attr('role'); // Cached attribute access
const nodeName = virtualNode.props.nodeName;

// Only access real node when you need DOM APIs
const rect = node.getBoundingClientRect();
const rootNode = node.getRootNode();
}

// Convert ambiguous input using nodeLookup
import nodeLookup from '../../core/utils/node-lookup';

function myFunction(nodeOrVirtual) {
const { vNode, domNode } = nodeLookup(nodeOrVirtual);
// vNode = VirtualNode, domNode = real DOM node
}
```
Loading
Loading