aria icon indicating copy to clipboard operation
aria copied to clipboard

Expectations for aria-hidden and focused elements

Open scottaohara opened this issue 1 year ago β€’ 25 comments

This PR closes #1765 and is related to work that was done in #2037, but scoped only to the original issue I filed.

The intent of this PR is to identify not only how user agents would need to handle focusable elements that are aria-hidden (explicitly or due to being a descendant of an aria-hidden container) - but for the case where a focusable element is within an aria-hidden container, that the entire subtree would need to be re-exposed so that any other relevant information to the user could be made available. (e.g., so as to not just expose a "learn more" link, with no way to determine what someone would be learning about)

a simple example being like:

<div aria-hidden=true>
  <h3>Something or other</h3>
   some details about said something, or other.
  <a href=#>Learn more!</a>
</div>
  • [ ] Related Core AAM Issue/PR:
  • [ ] Related AccName Issue/PR:
  • [ ] Any other dependent changes?

Test, Documentation and Implementation tracking

Once this PR and all related PRs have been approved by the working group, tests should be written and issues should be opened on browsers. Add N/A and check when not applicable.

  • [ ] Related APG Issue/PR:
  • [ ] MDN Issue/PR:
  • [ ] "author MUST" tests:
  • [ ] "user agent MUST" tests:
  • [ ] Browser implementations (link to issue or when done, link to commit):
    • WebKit:
    • Gecko:
    • Blink:
  • [ ] Does this need AT implementations?

Preview | Diff

scottaohara avatar May 20 '24 19:05 scottaohara

That seems like a good idea, and also, to not use aria-hidden if something is already inert / display-none etc.

On Mon, May 20, 2024 at 3:41β€―PM scottaohara @.***> wrote:

@.**** commented on this pull request.

In index.html https://github.com/w3c/aria/pull/2181#discussion_r1607194297:

@@ -11604,6 +11604,7 @@

Definitions of States and Properties (all aria-* attributes)

Indicates, when set to true, that an element and its entire subtree are hidden from assistive technology, regardless of whether it is visibly rendered.

User agents determine an element's [=element/hidden=] status based on whether it is rendered, and the rendering is usually controlled by CSS. For example, an element whose display property is set to none is not rendered. An element is considered [=element/hidden=] if it, or any of its ancestors are not rendered or have their aria-hidden attribute value set to true.

Authors MAY, with caution, use aria-hidden to hide visibly rendered content from assistive technologies only if the act of hiding this content is intended to improve the experience for users of assistive technologies by removing redundant or extraneous content. Authors using aria-hidden to hide visible content MUST ensure that identical or equivalent meaning and functionality is exposed to assistive technologies.

  • 		<p>Additionally, authors SHOULD ensure that any elements that are accessibility descendants of an aria-hidden element, or any elements which have been marked as aria-hidden themselves, are prevented from receiving focus. If an aria-hidden element or a descendant of an aria-hidden ancestor receives focus, User Agents MUST ignore the aria-hidden state of any ancestor to the focused element, resulting in the entire subtree to be exposed to assistive technologies from that point forward, even once the element loses focus or is no longer focusable.</p>
    

i know this section is already note-heavy, but i was debating on whether it was worth mentioning that authors SHOULD consider using host language features, like inert or display: none to properly hide content and thus mitigate needing to worry about this at all.

β€” Reply to this email directly, view it on GitHub https://github.com/w3c/aria/pull/2181#discussion_r1607194297, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKQAZSVE5APGONCM3M3IULZDJGXJAVCNFSM6AAAAABIAINPZCVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDANRWHA4TINZWHA . You are receiving this because you commented.Message ID: <w3c/aria/pull/2181/review/2066894768 @.***>

aleventhal avatar May 20 '24 20:05 aleventhal

This may be WPT-testable; should I create a separate issue if you'd like tests for this (the MUST around the subtree being re-exposed)?

rahimabdi avatar May 21 '24 20:05 rahimabdi

talking with @smhigley about this... but should this clarify that if an element is already focused and its container becomes aria-hidden, then the aria-hidden state should remain unless focus is then moved to another element within that hidden subtree.

for instance, think of a custom modal dialog that was built where the order of events are:

  • button invokes dialog (focus is on button)
  • element containing button set to aria-hidden=true
  • dialog is rendered (outside aria-hidden=true container)
  • focus moves to dialog

since focus moving is the last thing that occurs, we do not want browsers to immediately undo the aria-hidden that was dynamically added (though focus is still on the button inside the aria-hidden container)

scottaohara avatar May 21 '24 21:05 scottaohara

need to add to this that user agents need to make this repair if the element focused is in the same document - but say if the element that is focused is within an iframe, but the iframe is hidden from an aria-hidden surrounding it in the parent doc, then browsers may not be able to reasonably make this repair.

scottaohara avatar May 28 '24 20:05 scottaohara

I have one other question, but mostly for browser implementors; I'm not sure if this amount of detail can be in the spec --

Would the removal of aria-hidden occur after the focus event on an element, or after document.activeElement is set to point to a hidden element? I'm making the distinction because in a lot of our focus management logic for things like dialogs, we look for a focus event on a hidden element (either a focus bumper, or looking for focus moving to the background page), and immediately send focus back into the dialog in the event handler. The result is that document.activeElement never points to a hidden element, but the focus event does fire. If the change happened as a result of the event, it would break quite a lot of things at least in our library, and IIRC in others as well πŸ˜….

If that's not the case, and if the scenario that @scottaohara already mentioned is also handled, I think I wouldn't have any more concerns about this.

smhigley avatar May 28 '24 21:05 smhigley

We aren't actually changing the DOM, aka doing a removeAttribute(). We are just adding that node to a list of nodes where we will ignore it's aria-hidden markup from now on. Because of this, there shouldn't be any ordering questions. It means that in the a11y update that contains the focus event, the subtree's content will be there unhidden.

On Tue, May 28, 2024 at 5:28β€―PM Sarah Higley @.***> wrote:

I have one other question, but mostly for browser implementors; I'm not sure if this amount of detail can be in the spec --

Would the removal of aria-hidden occur after the focus event on an element, or after document.activeElement is set to point to a hidden element? I'm making the distinction because in a lot of our focus management logic for things like dialogs, we look for a focus event on a hidden element (either a focus bumper, or looking for focus moving to the background page), and immediately send focus back into the dialog in the event handler. The result is that document.activeElement never points to a hidden element, but the focus event does fire. If the change happened as a result of the event, it would break quite a lot of things at least in our library, and IIRC in others as well πŸ˜….

If that's not the case, and if the scenario that @scottaohara https://github.com/scottaohara already mentioned is also handled, I think I wouldn't have any more concerns about this.

β€” Reply to this email directly, view it on GitHub https://github.com/w3c/aria/pull/2181#issuecomment-2136140199, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKQAZW2PGKDNACEN27QSY3ZETZGRAVCNFSM6AAAAABIAINPZCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZWGE2DAMJZHE . You are receiving this because you commented.Message ID: @.***>

aleventhal avatar May 28 '24 21:05 aleventhal

@aleventhal I think I may not have communicated my use case well enough πŸ˜….

The distinction between doing a .removeAttribute() and ignoring the attribute through some other method wasn't really material to the question I had in mind, though bringing it up does make me worry that this may make it difficult for developers to debug when an element hidden in markup is exposed.

For my original question, I made this JS Fiddle to help: https://jsfiddle.net/sjc89htb/. It uses logic similar to what we use at Microsoft, and what I've seen for focus traps elsewhere, where focus is immediately sent elsewhere when it lands on a hidden element. In practice this works perfectly in all ATs I've tested up till now. Would you be able to clarify whether it will continue working with this change, or whether the change will result in the hidden content being exposed?

Thanks for taking the time to investigate and weigh in!

smhigley avatar May 29 '24 05:05 smhigley

@aleventhal I think I may not have communicated my use case well enough πŸ˜….

Thanks for explaining... good chance the issue was on the receiver's end :)

The distinction between doing a .removeAttribute() and ignoring the attribute through some other method wasn't really material to the question I had in mind, though bringing it up does make me worry that this may make it difficult for developers to debug when an element hidden in markup is exposed.

We could log something like an error to the JS console, warning the developer that bad aria-hidden markup had been discarded. I thought we had similar guidance for other user agent repairs, but I can't find that in the spec after a quick search.

For my original question, I made this JS Fiddle to help: https://jsfiddle.net/sjc89htb/. It uses logic similar to what we use at Microsoft, and what I've seen for focus traps elsewhere, where focus is immediately sent elsewhere when it lands on a hidden element. In practice this works perfectly in all ATs I've tested up till now. Would you be able to clarify whether it will continue working with this change, or whether the change will result in the hidden content being exposed?

It seems to work! Our proposed change is currently implemented in Chrome Canary from this morning, version 127.0.6508.0. I ran with and without the focus trap. In the version without the focus trap, once you tab into the button, the button is revealed in the a11y tree (I used the devtools Inspector full page a11y view for the test). In the version with the focus trap, focus never goes to the button, and the aria-hidden is never discarded. I think this is an important test that we should add to WPT tests. @scottaohara maybe we can update the spec text to say if focus is not forwarded somewhere else? Perhaps it should mentioned as a valuable technique, although maybe authors should be using inert here.

Anyway, go ahead and play with Canary if you like. I'll wait to hear back what people think of a console message to developers. Maybe something like, console.error("The following element had aria-hidden markup that was discarded, because focus went to it or an element within it. See WAI-ARIA rules on aria-hidden:", bad_aria_hidden_element);

aleventhal avatar May 29 '24 14:05 aleventhal

@aleventhal awesome! I love both the idea of putting that scenario in WPT, and the idea of logging a warning to the console.

I'm all in on this now, thanks again for investigating!

smhigley avatar May 29 '24 18:05 smhigley

Landing a patch to Chromium that adds this console error:

  element.AddConsoleMessage(
      mojom::blink::ConsoleMessageSource::kRendering,
      mojom::blink::ConsoleMessageLevel::kError,
      String::Format(
          "Blocked aria-hidden on a <%s> element because the element that just "
          "received focus must not be hidden from assistive technology users. "
          "Avoid using aria-hidden on a focused element or its ancestor. "
          "Consider using the inert attribute instead, which will also prevent "
          "focus. For more details, see the aria-hidden section of the "
          "WAI-ARIA specification at https://w3c.github.io/aria/#aria-hidden.",
          element.TagQName().ToString().Ascii().c_str()));

Should we add text to this PR saying that user agents may provide a helpful console error message? Also, can we update this PR to say how developers can fix the issue? (Either by forwarding focus via focusin, by not putting aria-hidden on focusable objects, by using inert etc.)

aleventhal avatar May 30 '24 17:05 aleventhal

Deploy Preview for wai-aria ready!

Name Link
Latest commit ac3983862c83bc76ebf93c498402375ef86eb26e
Latest deploy log https://app.netlify.com/sites/wai-aria/deploys/679bcb067d86c90008389baf
Deploy Preview https://deploy-preview-2181--wai-aria.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

netlify[bot] avatar Aug 07 '24 18:08 netlify[bot]

made updates per the provided feedback, so re-requesting reviews.

scottaohara avatar Aug 07 '24 18:08 scottaohara

Landing a patch to Chromium that adds this console error:

  element.AddConsoleMessage(
      mojom::blink::ConsoleMessageSource::kRendering,
      mojom::blink::ConsoleMessageLevel::kError,
      String::Format(
          "Blocked aria-hidden on a <%s> element because the element that just "
          "received focus must not be hidden from assistive technology users. "
          "Avoid using aria-hidden on a focused element or its ancestor. "
          "Consider using the inert attribute instead, which will also prevent "
          "focus. For more details, see the aria-hidden section of the "
          "WAI-ARIA specification at https://w3c.github.io/aria/#aria-hidden.",
          element.TagQName().ToString().Ascii().c_str()));

Should we add text to this PR saying that user agents may provide a helpful console error message? Also, can we update this PR to say how developers can fix the issue? (Either by forwarding focus via focusin, by not putting aria-hidden on focusable objects, by using inert etc.)

@scottaohara

aleventhal avatar Aug 29 '24 20:08 aleventhal

per my last commit - it looks like @giacomo-petri had already tried to fit the errant ending p tag (https://github.com/w3c/aria/pull/2244) but since this PR was made before that, it was still here.... and since the errant ending tag is still in the current spec - it clearly got re-introduced by a commit since Giacomo's.

scottaohara avatar Sep 26 '24 16:09 scottaohara

I believe I understand the reason behind this:

...omissis... are either prevented from receiving focus, or upon receiving focus use scripting to immediately move focus to another element.,

However, in the context of the following sentence:

The ancestor aria-hidden markup will continue to be ignored, even once the element loses focus or is no longer focusable. User agents MAY provide console messages to warn or flag aria-hidden errors to developers.

this could introduce issues, potentially causing a discrepancy between the experience of assistive technologies (where everything is exposed) and keyboard navigation (where the operable control is only used for functional reasons for moving focus somewhere else).

Should we be more strict by removing "either" and stating that authors should ensure these aria-hidden true elements do not receive focus at all? The sentence already begins with "authors SHOULD," which is less strict than "MUST NOT," and we are not prohibiting focus management via JavaScript. However, without "either," I think the guidance would likely be clearer.

giacomo-petri avatar Sep 26 '24 17:09 giacomo-petri

@giacomo-petri that text was deliberately added in because there are component libraries in use that do this without issue.

having div tabindex=0 aria-hidden=true bumpers at the start/end of a component that needs to 'trap' tab key focus. from what i've been told in talking about this with at least one component library (ahem @smhigley) is that this is far more performant than querying all the elements within the container, and needing to make dynamic adjustments for the instances where the focusable elements (particularly first/last) might change, and having to re-determine what elements should loop focus again/where the new focus should go to.

I am not aware of where this actually hurts anything - but automated checkers flag this up and down and I can tell you that I've seen a report of over 2500 instances of this "issue" across a select few products, and all 2500 are considered a "false positive" because of the effort that went into making sure that there would be no negative user impact.

That said, I have also done recent testing to this pattern and haven't found much in the way of a difference to whether the aria-hidden exists on that div or not.... but I also have not had the time to go into the deep level of testing that I know others have on this in support of adding the attribute in the first place. (please note this is not just used for ARIA modal dialog purposes, but other instances where trapping tab key might be necessary, while implementing other keyboard navigation behaviors). i'm just hesitant at this point to do anything else with this text, since it's already been word smithed into its current state to account for this scenario. If others have suggestions though that can maintain the intent without undoing what people wanted in there, then please, have at it :)

scottaohara avatar Sep 26 '24 23:09 scottaohara

What's the status of landing this? I occasionally get asked about the message in the Chrome dev tools console, and would like to be able to link into the new wording right in the spec, so there is no doubt :)

aleventhal avatar Jan 24 '25 20:01 aleventhal

One change I'm making to Chrome's implementation is to wait a tiny bit before logging the error and discarding the aria-hidden. This is helpful in cases where the author uses setTimeout(0)/requestAnimationFrame() before deciding on the new focus somewhere appropriate. This was requested by Angular Material, who thought it was common practice. For example, the script marks the main content as aria-hidden and adds the dialog, but waits for the dialog to be rendered before determining what the first focusable object in it is that should be focused. Here's the issue where that's discussed: https://issues.chromium.org/issues/392121909 We can provide an example in a code pen if anyone needs it. This might be considered an implementation detail β€” I'm not sure how we would tweak the wording in this PR.

aleventhal avatar Jan 30 '25 15:01 aleventhal

There does seem to be a one-time timing mismatch between setting aria-hidden and setting focus when I tried to code a pretty bare-bones standard dialog approach in React as well: https://stackblitz.com/edit/react-template-fb5ctn9x?file=App.js,package.json,index.js

(For a bit of explanation for anyone who tries the stackblitz: Chrome throws the console error when the custom dialog is opened and focused for the first time, but not subsequent times. The difference is due to internal logic in React, not Chrome, if I remember correctly. This issue disappears with changes in React 18.)

I tried to write the logic based on what I believe would be the most common way to code the combination between aria-hidden updates and element.focus() calls. Those results are in line with my gut feeling, which is that it's unreliable to depend on the timing of whether or not focus has left a region before aria-hidden is added. Often authors/framework patterns are structured so that the static DOM updates have finished before element.focus() is called -- the intent of doing so is to ensure that the new to-be-focused element actually exists when you attempt to focus it, and that element creation often happens in the same JS cycle as the aria-hidden change.

What do folks (and by folks I mostly mean @aleventhal) think of the idea of checking element hidden status when focus moves, rather than when aria-hidden is applied? I believe it's a much safer author requirement to say that focus must never enter an aria-hidden element, and I think that requirement is more in line with what actually causes problems for screen reader users. Also the idea of needing to add a timeout to the check feels kinda bad just on a gut level, but also I don't know anything about the browser implementation details, so I'll defer to those who do :D

(also apologies for not mentioning something before -- the library I work on actually uses a somewhat unconventional outside-of-React aria-hidden/focus management approach, which does not trigger the new console error)

(also I'm not trying to suggest React should be the barometer of what works or doesn't, I was just testing it to get a sense of how widespread this timing problem might be)

smhigley avatar Feb 03 '25 19:02 smhigley

Hi @smhigley Let's try again after I land my fix. Hopefully this week.

aleventhal avatar Feb 03 '25 22:02 aleventhal

@scottaohara I tried and failed to update to main branch (merge and rebase). Sorry for failing on my promise :pensive:

I suspect it'll be easier time to start with a fresh branch and apply the final changes once review is complete.

pkra avatar Mar 13 '25 11:03 pkra

@Sarah Higley @.***> I don't think we only want the repair when focus moves into it, as opposed to aria-hidden being applied around the focus. Either way, there's no legal ax tree focus at that moment.

The real fix for the Material issue is probably to give the website a moment to move the focus just after, e.g. give it 10ms to fix things before we do. Unfortunately, my first attempt at this caused some instability so had to be reverted. I need to revisit the patch to see what went wrong, but don't have time right now.

For now, in cases where the author moves focus to a better place on a delay, I agree it's not ideal but I'm not aware of cases where the screen reader user can't access things. On the other hand, if we remove the repair, we know things will break. We're going to hear about a few cases where the repair wasn't right, but I've also already heard of 5+ cases where the repair helped and was thanked by a developer who saw the error and fixed their code.

On Thu, Mar 13, 2025 at 7:50β€―AM Peter Krautzberger @.***> wrote:

@scottaohara https://github.com/scottaohara I tried and failed to update to main branch (merge and rebase). Sorry for failing on my promise πŸ˜”

I suspect it'll be easier time to start with a fresh branch and apply the final changes once review is complete.

β€” Reply to this email directly, view it on GitHub https://github.com/w3c/aria/pull/2181#issuecomment-2720990858, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKQAZU6WF23WPXAYBHVWML2UFWJRAVCNFSM6AAAAABIAINPZCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMRQHE4TAOBVHA . You are receiving this because you were mentioned.Message ID: @.***> [image: pkra]pkra left a comment (w3c/aria#2181) https://github.com/w3c/aria/pull/2181#issuecomment-2720990858

@scottaohara https://github.com/scottaohara I tried and failed to update to main branch (merge and rebase). Sorry for failing on my promise πŸ˜”

I suspect it'll be easier time to start with a fresh branch and apply the final changes once review is complete.

β€” Reply to this email directly, view it on GitHub https://github.com/w3c/aria/pull/2181#issuecomment-2720990858, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKQAZU6WF23WPXAYBHVWML2UFWJRAVCNFSM6AAAAABIAINPZCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMRQHE4TAOBVHA . You are receiving this because you were mentioned.Message ID: @.***>

aleventhal avatar Mar 13 '25 15:03 aleventhal

Personally I was going to give it one animation frame and then checking again before performing the repair. I can't think of a reason a script should need more time than that.

On Tue, Mar 18, 2025 at 6:21β€―PM Dominic Mazzoni @.***> wrote:

@.**** commented on this pull request.

In index.html https://github.com/w3c/aria/pull/2181#discussion_r2002108469:

@@ -11774,8 +11774,10 @@

Definitions of States and Properties (all aria-* attributes)

Indicates, when set to true, that an element and its entire subtree are hidden from assistive technology, regardless of whether it is visibly rendered.

User agents determine an element's [=element/hidden=] status based on whether it is rendered, and the rendering is usually controlled by CSS. For example, an element whose display property is set to none is not rendered. An element is considered [=element/hidden=] if it, or any of its ancestors are not rendered or have their aria-hidden attribute value set to true.

  • 		<p>Authors MUST NOT use <code>aria-hidden</code> to hide the root element or the host language element that <a data-cite="html/dom.html#represents">represents</a> the contents of the primary document in view. For instance, the <code>html</code> or <code>body</code> elements in an HTML document. Authors MAY, with caution, use <code>aria-hidden</code> to hide visibly rendered content from assistive technologies <em>only</em> if the act of hiding this content is intended to improve the experience for users of assistive technologies by removing redundant or extraneous content. Authors using <code>aria-hidden</code> to hide visible content from screen readers MUST ensure that identical or equivalent meaning and functionality is exposed to assistive technologies.</p>
    
  • 		<p class="note">Authors are advised to use extreme caution and consider a wide range of disabilities when hiding visibly rendered content from assistive technologies. For example, a sighted, dexterity-impaired individual might use voice-controlled assistive technologies to access a visual interface. If an author hides visible link text "Go to checkout" and exposes similar, yet non-identical link text "Check out now" to the accessibility API, the user might be unable to access the interface they perceive using voice control software. Similar problems can also arise for screen reader users. For example, a sighted telephone support technician might attempt to have the blind screen reader user click the "Go to checkout" link, which they might be unable to find using a type-ahead item search ("Go to…"), since that text would have been hidden by the use of the attribute.</p>
    
  • 		<p>Authors MUST NOT use <code>aria-hidden</code> to hide the root element or the host language element that <a data-cite="html/dom.html#represents">represents</a> the contents of the primary document in view. For instance, the <code>html</code> or <code>body</code> elements in an HTML document. Authors MAY, with caution, use <sref>aria-hidden</sref> to hide visibly rendered content from assistive technologies <em>only</em> if the act of hiding this content is intended to improve the experience for users of assistive technologies by removing redundant or extraneous content. Authors using <sref>aria-hidden</sref> to hide visible content MUST ensure that identical or equivalent meaning and functionality is exposed to assistive technologies. Whenever possible it's strongly recommended authors use host language features to <a href="#tree_exclusion">exclude elements from the accessibility tree</a>.</p>
    
  • 		<p>Additionally, authors SHOULD ensure that any elements that are accessibility descendants of an aria-hidden element, or any elements which have been marked as aria-hidden themselves, are either prevented from receiving focus, or upon receiving focus use scripting to immediately move focus to another element.</p>	
    
  • 		<p>User Agents MUST ignore the aria-hidden state of any ancestor to the focused element from that point forward, resulting in the entire subtree to be exposed to assistive technologies. The ancestor aria-hidden markup will continue to be ignored, even once the element loses focus or is no longer focusable.  User agents MAY provide console messages to warn or flag aria-hidden errors to developers.</p>
    

I like the idea, it just seems ambiguous and tricky to implement in a reliable, predictable way.

Is what you had in mind something like: if focus lands on something in an aria-hidden region, then the browser waits until it's sure all event listeners have run, and then if focus is still there, it permanently exposes that region?

Does it fire the focus event and temporarily un-hide the region immediately, but defer the decision to expose it permanently until it's sure all event listeners have run? Or does it defer even firing the accessibility focus event until it decides?

β€” Reply to this email directly, view it on GitHub https://github.com/w3c/aria/pull/2181#discussion_r2002108469, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKQAZSZPFANRFCRW3DKYZT2VCL7JAVCNFSM6AAAAABIAINPZCVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDMOJWGQ2DONBQHE . You are receiving this because you were mentioned.Message ID: <w3c/aria/pull/2181/review/2696447409 @.***>

aleventhal avatar Mar 19 '25 01:03 aleventhal

If we go this route, I think the details need to be part of the specification.

Something like: if an element gets focus and it is effectively aria-hidden (by itself or on an ancestor), the user agent MUST treat the entire aria-hidden subtree as not hidden. Furthermore, if the element gets focus and after running all installed event listeners, focus remains on that element, then the user agent should mark the ancestor as permanently not hidden.

I still have some concerns, though. What happens if focus lands on the element while JS is still loading and installing event listeners?

I think it'd probably be safer to just only make it not hidden while focus is inside, rather than making permanent changes to the page.

minorninth avatar Mar 19 '25 21:03 minorninth

I think it'd probably be safer to just only make it not hidden while focus is inside, rather than making permanent changes to the page.

Let's try prototyping that in each of our browsers. I originally thought it would be easier to just put the aria-hidden markup on the naughty list, but your idea sounds like it would get around some things. I'd still plan on logging a warning or error to the console after 100ms or something like that, if the focus is still inside the aria-hidden. WDYT?

aleventhal avatar Mar 19 '25 23:03 aleventhal

I think it'd probably be safer to just only make it not hidden while focus is inside, rather than making permanent changes to the page.

I have a couple of concerns with this if a user is navigating all over the place. Imagine a form which is incorrectly aria-hidden, but the user is also referring to other parts of the page which aren't aria-hidden.

  1. It could result in a lot of spurious tree mutation. The user moves focus inside the form, the form gets added to the tree. The user moves out, the form gets removed from the tree. etc. etc. That could cause performance problems in certain situations depending on the client, OS and size of the aria-hidden subtree.
  2. It might result in a situation where focus goes inside the aria-hidden content, the user moves focus out of it to look at something else, and then they can't get back to the aria-hidden content they were previously working with. It isn't intuitive that the user can stab in the dark with the tab key until they find the mysteriously auto-vanishing content. They'll just think it's been removed.

Once we make a determination (per spec) that the author has misused aria-hidden, I'd argue we should treat it as misuse full stop and ignore it. That results in a more consistent user experience.

jcsteh avatar Sep 18 '25 03:09 jcsteh

What happens if focus lands on the element while JS is still loading and installing event listeners?

The animation frame proposal, while a little obscure, does avoid this problem. Rather than tying this to event listeners (which can be somewhat indeterminate in some cases as you point out), an animation frame is a clearly defined unit and thus easier to reason about. Hopefully. 😰

jcsteh avatar Sep 18 '25 03:09 jcsteh

If we go this route, I think the details need to be part of the specification.

I agree; this should not be implementation specific, as I think this could be a pretty significant cause of hard-to-understand interop problems. Aside from that, the changes here look good to me.

jcsteh avatar Sep 18 '25 04:09 jcsteh