fix(aria-allowed-role): restrict figure roles with child figcaption - #5240
Merged
Conversation
A figure with a child figcaption is now limited to its implicit figure role and the DPub doc-example role, per ARIA in HTML (w3c/html-aria#415). A figure without a figcaption continues to allow any role. Closes #3443
straker
requested changes
Jul 23, 2026
chutchins25
force-pushed
the
chut/3443-figure-figcaption
branch
from
July 29, 2026 15:10
d39b1f3 to
a63c18b
Compare
chutchins25
marked this pull request as ready for review
July 29, 2026 17:43
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates axe-core’s ARIA-in-HTML modeling for <figure> to reflect the spec change that a figure with a child <figcaption> is restricted to its implicit role (figure, handled via allowImplicit) and the explicit DPub role doc-example, while a figure without <figcaption> continues to allow any explicit role.
Changes:
- Updated
figure’sallowedRolesin the HTML standards map to use avariantthat detects a direct-childfigcaptionand restricts roles accordingly. - Added/updated unit tests for element spec resolution, role-allowed logic, and the
aria-allowed-rolecheck (including Shadow DOM coverage). - Added integration + virtual-rule test coverage for pass/fail cases involving
figure+figcaption.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
lib/standards/html-elms.js |
Introduces a figure variant that restricts allowedRoles to ['doc-example'] when a direct-child figcaption is present; otherwise defaults to allowedRoles: true. |
test/commons/standards/get-element-spec.js |
Verifies variant resolution for figure with/without a figcaption child. |
test/commons/aria/is-aria-role-allowed-on-element.js |
Adds direct unit coverage that doc-example is allowed and group is disallowed for figure with figcaption. |
test/checks/aria/aria-allowed-role.js |
Adds check-level tests (including Shadow DOM) for the new figure+figcaption role restriction behavior. |
test/integration/virtual-rules/aria-allowed-role.js |
Adds virtual-rule coverage using SerialVirtualNode for figure+figcaption pass/fail and the no-figcaption regression case. |
test/integration/rules/aria-allowed-role/aria-allowed-role.html |
Adds integration fixtures for figure+figcaption with doc-example/figure (pass) and group (fail). |
test/integration/rules/aria-allowed-role/aria-allowed-role.json |
Registers the new integration fixture selectors in passes/violations lists. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
chutchins25
marked this pull request as draft
July 29, 2026 20:18
hasChild threw when vNode.children was undefined (e.g. a SerialVirtualNode passed to runVirtualRule without children). Treat a missing children list as no match.
…variant Replace the inline condition function in the figure figcaption variant with the serializable hasChild: 'figcaption' matcher from #5262 so the standards object stays function-free. Behavior is unchanged.
chutchins25
marked this pull request as ready for review
August 3, 2026 20:23
straker
requested changes
Aug 3, 2026
straker
reviewed
Aug 3, 2026
Per review: drop the hasChild children guard (children are assumed present), remove the spec-defined variant comments, drop the duplicated check and virtual-rule tests (integration covers them), and add a get-element-spec test that a nested figcaption does not restrict the outer figure.
straker
approved these changes
Aug 4, 2026
WilcoFiers
added a commit
that referenced
this pull request
Aug 5, 2026
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. ## [4.13.0](v4.12.1...v4.13.0) (2026-08-05) ### Features - **aria-actions:** add aria-actions to allowed ARIA attributes ([#5200](#5200)) ([029655d](029655d)), closes [#4584](#4584) [#5199](#5199), references [#5215](#5215) [#5215](#5215) - **aria-allowed-attr:** flag deprecated ARIA attributes as needs-review ([#5246](#5246)) ([518f3cc](518f3cc)), closes [#3341](#3341) - **aria-prohibited-attr:** allow many elements to be named and disallow label and body from being named ([#5259](#5259)) ([d8b1ea5](d8b1ea5)) - **aria-roles:** add sectionheader and sectionfooter roles ([#5238](#5238)) ([c36c109](c36c109)), closes [#4734](#4734), references [#4734](#4734) - **aria/get-aria-value:** new function to get aria values of a node ([#5109](#5109)) ([a7d8f3e](a7d8f3e)), references [#5042](#5042) - **aria/has-attr-value:** new function to check if node has aria value ([#5136](#5136)) ([61f2624](61f2624)), references [#5109](#5109) - **aria:** support role=image as equivalent to role=img ([#5248](#5248)) ([5aa8aaf](5aa8aaf)), closes [#4656](#4656), references [#5272](#5272) - **checks/aria:** support ARIA element internals properties ([#5172](#5172)) ([9b7f754](9b7f754)) - **checks/label:** support ARIA element internals properties ([#5170](#5170)) ([21c5f8b](21c5f8b)) - **checks/navigation:** support ARIA element internals properties ([#5167](#5167)) ([2c3a98f](2c3a98f)) - **commons/aria:** support ARIA element internals properties ([#5171](#5171)) ([31f09e7](31f09e7)) - **commons/dom:** support ARIA element internals properties ([#5163](#5163)) ([f0a12cf](f0a12cf)) - **commons/forms:** support ARIA element internals properties ([#5165](#5165)) ([27a4686](27a4686)) - **commons/matches/fromPrimative:** deprecate in favor of correct spelling ([#5270](#5270)) ([31cfb2e](31cfb2e)) - **commons/text:** support ARIA element internals properties ([#5169](#5169)) ([e841a33](e841a33)) - **commons/text:** support form-associated labels via element internals ([#5182](#5182)) ([57cfe0a](57cfe0a)), closes [#5045](#5045), references [#5170](#5170) [#5039](#5039) [#5151](#5151) [#5039](#5039) - **dom/getResolvedRefs:** new function to get the resolved virtual nodes of idrefs ([#5151](#5151)) ([489cdea](489cdea)), references [#5109](#5109) - **element-internals:** enable ElementInternals by default ([#5284](#5284)) ([2740d42](2740d42)), closes [#5277](#5277) - **i18n:** Add Swedish locale ([#5190](#5190)) ([dcd13f2](dcd13f2)), references [#5189](#5189) - **matches:** add inSectioningContent, hasChild, and isSummaryForDetails matches ([#5262](#5262)) ([c47cdcd](c47cdcd)) - **rules:** support ARIA element internals properties ([#5168](#5168)) ([065baf7](065baf7)) - **standards/ariaAttrs:** add caseInsensitive property for attributes ([#5224](#5224)) ([bcd791c](bcd791c)) ### Bug Fixes - **aria-allowed-role:** allow roles on a non-details summary ([#5242](#5242)) ([3bd9875](3bd9875)), closes [#3911](#3911), references [#3443](#3443) [#3911](#3911) - **aria-allowed-role:** restrict figure roles with child figcaption ([#5240](#5240)) ([178a635](178a635)), closes [#3443](#3443) - **aria-prohibited-attr:** visible aria-labelledby requires review only ([#5285](#5285)) ([fd6fa9f](fd6fa9f)) - **axe.d.ts:** make enabled property of RuleMetadata optional ([#5129](#5129)) ([90fce18](90fce18)) - **color-contrast:** fix various stacking context bugs ([#5214](#5214)) ([d5e5b04](d5e5b04)), references [#8](#8) [#5213](#5213) - **gather-internals:** handle non-HTMLElement nodes ([#5161](#5161)) ([06e84c3](06e84c3)) - **get-selector:** escape control characters in attribute selectors ([#5273](#5273)) ([4b60ac5](4b60ac5)), closes [#5204](#5204) [#5204](#5204) - **image-alt:** allow whitespace alt on presentational images ([#5218](#5218)) ([c5dd0ef](c5dd0ef)), closes [#5216](#5216) - **landmark-unique:** exclude section/form with non-landmark roles from landmark match ([#5085](#5085)) ([c5fd013](c5fd013)), closes [#4722](#4722) [#5064](#5064) - name the image role in role-img-alt and svg-img-alt metadata ([#5279](#5279)) ([995a269](995a269)), closes [#5272](#5272), references [#5248](#5248) [#5248](#5248) - **standards:** update aria-errormessage and aria-details to be idrefs ([#5157](#5157)) ([fb94f8a](fb94f8a)) This PR was opened by a robot 🤖 🎉
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restricts the roles allowed on a
<figure>element when it has a child<figcaption>, per the ARIA in HTML update.What & why
Per ARIA in HTML (
#el-figure, w3c/html-aria#415):figurewith a validfigcaptiondescendant allows no role other thanfigure, plus the DPub roledoc-example.figurewithout afigcaptionallows any role (thoughfigureitself is NOT RECOMMENDED) — unchanged.axe previously modeled
figurewith a flatallowedRoles: trueand an inline comment admitting it couldn't match the figcaption case. This replaces that with avariantusing aconditionmatcher.Implementation notes
figurerole still passes via the existingallowImplicitshort-circuit, so the with-figcaption branch only needsallowedRoles: ['doc-example'].vNode.children?.some(...)), not a descendant walk: HTML's content model only permitsfigcaptionas the first/last child offigure, and a direct-child check avoids a nested-<figure>false match (an inner figure's figcaption wrongly restricting the outer figure).Behavior change
A
<figure>with a<figcaption>and an explicit role other thanfigure/doc-example(includingrole="none"/role="presentation") now flags onaria-allowed-role(minor impact). This is spec-correct but will surface new findings for existing users.Tests
get-element-specunit — variant resolution with/without figcaption.is-aria-role-allowed-on-elementandaria-allowed-rolecheck units — incl. an open Shadow DOM case.aria-allowed-rolevirtual-rule — figcaption-child cases (built withSerialVirtualNode) plus the no-figcaption default.aria-allowed-roleintegration HTML/JSON — pass (doc-example, implicitfigure) and violation (group) cases; existing no-figcaption pass retained as a regression anchor.Closes #3443