Skip to content

Conversation

@rcj-siteimprove
Copy link
Contributor

Resolves #1140

@changeset-bot
Copy link

changeset-bot bot commented Dec 17, 2025

🦋 Changeset detected

Latest commit: adfaea1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 80 packages
Name Type
@siteimprove/alfa-aria Patch
@siteimprove/alfa-dom Patch
@siteimprove/alfa-rules Patch
@siteimprove/alfa-style Patch
@siteimprove/alfa-cascade Patch
@siteimprove/alfa-painting-order Patch
@siteimprove/alfa-selector Patch
@siteimprove/alfa-table Patch
@siteimprove/alfa-web Patch
@siteimprove/alfa-xpath Patch
@siteimprove/alfa-act Patch
@siteimprove/alfa-affine Patch
@siteimprove/alfa-applicative Patch
@siteimprove/alfa-array Patch
@siteimprove/alfa-bits Patch
@siteimprove/alfa-branched Patch
@siteimprove/alfa-cache Patch
@siteimprove/alfa-callback Patch
@siteimprove/alfa-clone Patch
@siteimprove/alfa-collection Patch
@siteimprove/alfa-comparable Patch
@siteimprove/alfa-compatibility Patch
@siteimprove/alfa-continuation Patch
@siteimprove/alfa-css-feature Patch
@siteimprove/alfa-css Patch
@siteimprove/alfa-device Patch
@siteimprove/alfa-eaa Patch
@siteimprove/alfa-earl Patch
@siteimprove/alfa-either Patch
@siteimprove/alfa-emitter Patch
@siteimprove/alfa-encoding Patch
@siteimprove/alfa-equatable Patch
@siteimprove/alfa-flags Patch
@siteimprove/alfa-fnv Patch
@siteimprove/alfa-foldable Patch
@siteimprove/alfa-functor Patch
@siteimprove/alfa-future Patch
@siteimprove/alfa-generator Patch
@siteimprove/alfa-graph Patch
@siteimprove/alfa-hash Patch
@siteimprove/alfa-http Patch
@siteimprove/alfa-iana Patch
@siteimprove/alfa-iterable Patch
@siteimprove/alfa-json-ld Patch
@siteimprove/alfa-json Patch
@siteimprove/alfa-lazy Patch
@siteimprove/alfa-list Patch
@siteimprove/alfa-map Patch
@siteimprove/alfa-mapper Patch
@siteimprove/alfa-math Patch
@siteimprove/alfa-monad Patch
@siteimprove/alfa-network Patch
@siteimprove/alfa-option Patch
@siteimprove/alfa-parser Patch
@siteimprove/alfa-performance Patch
@siteimprove/alfa-predicate Patch
@siteimprove/alfa-promise Patch
@siteimprove/alfa-record Patch
@siteimprove/alfa-rectangle Patch
@siteimprove/alfa-reducer Patch
@siteimprove/alfa-refinement Patch
@siteimprove/alfa-result Patch
@siteimprove/alfa-rng Patch
@siteimprove/alfa-sarif Patch
@siteimprove/alfa-selective Patch
@siteimprove/alfa-sequence Patch
@siteimprove/alfa-set Patch
@siteimprove/alfa-slice Patch
@siteimprove/alfa-string Patch
@siteimprove/alfa-test Patch
@siteimprove/alfa-thenable Patch
@siteimprove/alfa-thunk Patch
@siteimprove/alfa-time Patch
@siteimprove/alfa-toolchain Patch
@siteimprove/alfa-trampoline Patch
@siteimprove/alfa-tree Patch
@siteimprove/alfa-trilean Patch
@siteimprove/alfa-tuple Patch
@siteimprove/alfa-url Patch
@siteimprove/alfa-wcag Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@rcj-siteimprove
Copy link
Contributor Author

!pr extract

@rcj-siteimprove
Copy link
Contributor Author

!pr extract

@rcj-siteimprove rcj-siteimprove marked this pull request as ready for review December 17, 2025 13:52
@rcj-siteimprove rcj-siteimprove requested a review from a team as a code owner December 17, 2025 13:52
Copy link
Contributor

@Jym77 Jym77 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I think there is still a problem with open dialog deeply nested in inert containers.

const button = <button style={{ display: "none" }}>Hidden</button>;
h.document([button]);

t.equal(isProgrammaticallyHidden(button), true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note (non-blocking): t.equal(foo, true) is actually equivalent to t(foo); and t.equal(foo, false) to !t(foo), although I'm still not really sure which style I prefer… (explicitness vs compactness).

Comment on lines 282 to 290
if (this.attribute("inert").isSome()) {
this._isInert = true;
} else if (this.name === "dialog" && this.attribute("open").isSome()) {
this._isInert = false;
} else {
this._isInert = this.ancestors(Node.flatTree)
.find(Element.isElement)
.map((parent) => parent.isInert())
.getOr(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (this.attribute("inert").isSome()) {
this._isInert = true;
} else if (this.name === "dialog" && this.attribute("open").isSome()) {
this._isInert = false;
} else {
this._isInert = this.ancestors(Node.flatTree)
.find(Element.isElement)
.map((parent) => parent.isInert())
.getOr(false);
this._isInert = test(
or(
// Explicitly inert;
Element.hasAttribute("inert"),
and(
// or not an open dialog,
not(and(Element.hasName("dialog"), Element.hasAttribute("open"))),
// and with an inert ancestor.
element => element
.parent(Node.flatTree)
.filter(Element.isElement)
.some(parent =>parent.isInert())
)
),
this)

I'm not fully sure the predicate style is best here, though… I did find the this.attribute("inert").isSome() (and the likes) a bit tricky to read, and we do have access to the usual element predicates here, so I tried it that way… But it might be better to keep it low-level (not use the predicates) since we are in the package defining them 🤔

Comment on lines +433 to +435
if (state.isInert) {
return Inert.of(node);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Won't that break for deeply nested dialogs?

<div inert>
  <div>
    <dialog open>Hello</dialog>
  </div>
</div>

The outer <div> is a Container due to its inert attribute. The inner <div> is a Inert due to the state being set to inert by the parent, this means we never reach the dialog 🤔

I agree that we should set inert elements as Inert when possible, since we work on static snapshot, this will reduce the accessibility tree size and probably make some request faster.
Given that we do now have a Element#isInert predicate (and this seems to redo part of it), how about something like:

  • if inert is set, and no descendant is an open dialog not inert => Inert;
  • if inert is set and some descendant is not inert => Container;
  • if State.inert, then we have an inert Container ancestor:
    • if open dialog not inert => Element and reset State.inert;
    • if no descendant is not inert => Inert
    • otherwise, Container.
  • otherwise, as usual.

This probably requires some caching of "no descendant is not inert". Or maybe we can start by gathering all open dialogs (should be at most 1, iirc) and the set of their ancestors 🤔

Co-authored-by: Jean-Yves Moyen <jym@siteimprove.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for inert attribute

3 participants