Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to aria-hidden #2037

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
9 changes: 7 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11861,9 +11861,14 @@ <h2>Definitions of States and Properties (all aria-* attributes)</h2>
<div class="state" id="aria-hidden">
<sdef>aria-hidden</sdef>
<div class="state-description">
<p><a>Indicates</a> whether the <a>element</a> is exposed to an accessibility API. See related <sref>aria-disabled</sref>.</p>
<p><a>Indicates</a> whether the <a>element</a> can be included in the accessible tree as a visible accessibility node.</p>
Copy link
Member

Choose a reason for hiding this comment

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

not an objection, but not sure i understand the changing of this line. what is the difference between being included in the a11y tree (or a11y api) vs being included as a "visible" node. are these different things, or is this redundancy?

Copy link
Contributor

Choose a reason for hiding this comment

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

We want the freedom, on platforms that can express visibility as a boolean state (non-Apple platforms) to use that rather than pruning. This gives some power back to the AT to repair.

Copy link
Member

Choose a reason for hiding this comment

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

yeh i get that. i dunno, maybe i'll have a better way of framing the question tomorrow or something, i've been trying to re-ask the question for far too long now...

Copy link
Member

Choose a reason for hiding this comment

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

i get that the end goal here is to have more control over the 'visible' state of the node in the a11y tree. i question if this is the right way to introduce the attribute's purpose, particularly when the term 'visible' (and variants) are heavily used for whether something is visually rendered or not.

or another way to think about it, is aria-hidden identifying what nodes are visible in the a11y tree, or is it used to hide nodes (which can be re-made visible)? or is it being used to mark nodes as hidden OR visible (aria-hidden=false).

i hope that makes more sense? i'm not sure how else to describe this in text right now. the previous sentence made sense, this one doesn't (right now? maybe more changes are coming - it is a draft afer all) because it's unclear what the changing in words means since the rest of the text talks about hiding from the a11y tree.

Copy link
Contributor

Choose a reason for hiding this comment

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

These are good questions and will lead to more clarity. We would love your help with specific rewording suggestions to get there. I think we should start with what it does for the author, and then later discuss how the UA should apply that.

Copy link
Member

Choose a reason for hiding this comment

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

re: our talk today and looping in @MelSumner - i'm going to noodle on some ways to reword this and will submit that suggestion(s) asap

<p>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 <code>display</code> property is set to <code>none</code> is not rendered. An element is considered [=element/hidden=] if it, or any of its ancestors are not rendered or have their <code>aria-hidden</code> attribute value set to <code>true</code>.</p>
<p>Authors MAY, with caution, use aria-hidden 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 aria-hidden to hide visible content from screen readers MUST ensure that identical or equivalent meaning and functionality is exposed to assistive technologies.</p>
<p>While authors do not need to use <code>aria-hidden</code> when visibly hiding content with CSS, </code>authors MAY, with caution, use <code>aria-hidden="true"</code> to hide visibly rendered content from assistive technologies. However, authors should <em>only</em> do so if the act of hiding this content is intended to improve the experience for users of assistive technologies (e.g., to remove redundant or extraneous content). Authors using <code>aria-hidden="true"</code> to hide visible content from screen readers MUST ensure that identical or equivalent meaning and functionality is exposed to assistive technologies.</p>
Copy link
Member

Choose a reason for hiding this comment

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

While authors do not need to use aria-hidden when visibly hiding content with CSS

this should probably be more specific of where this is true, and/or allude to the fact that not all methods of visually hiding content with css will result in the content also being hidden from the accessibility tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While authors do not typically need to use aria-hidden when using CSS methods like display: none to visibly hide content...

@scottaohara would that address your concern?

Choose a reason for hiding this comment

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

Maybe "methods like display: none or visibility: hidden"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of "screen readers" use "assistive technologies". These are not interchangeable terms.

<p>User agents MUST either prune <code>aria-hidden</code> nodes from the accessibility tree, or mark all <code>aria-hidden</code> nodes as hidden.</p>

Choose a reason for hiding this comment

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

I'm assuming this paragraph is referring to aria-hidden nodes plus their descendants? If so I think it would be good to clearly call out that all descendants of an aria-hidden node need to be marked as hidden, not just the root node itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, correct. The value of aria-hidden (both true and false) inherits into descendants. An aria-hidden node is one where aria-hidden=true was set on the node itself, or it was inherited by a descendant.

Copy link

Choose a reason for hiding this comment

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

This optional "prune or mark" behaviour presents a few challenges:

  1. It's not clear whether this is a user agent choice or dependent on the particular platform.
  2. If it's a user agent choice, that isn't great for interop.
  3. If it's a platform choice, that means we have very different trees on different platforms. That creates some performance challenges for browser engines (at least engines that don't already do this). It's also not great for interop. Yes, different APIs are obviously different, but now we're talking about the tree structure being fundamentally different. If we're going to do this, I'd argue it should be consistent across platforms, which might mean changes to platform APIs across the board.
  4. How does this impact hit testing? If hit testing returns aria-hidden content, that means an AT might not be able to work out what is actually visible on the screen at that point if there are multiple visual layers (z-index, stacking contexts, etc.).
  5. I understand that the intent of this is to allow AT to do repair, but that in itself creates interop issues. As I noted in Proposal to include visible aria-hidden objects, at least in some platform trees #1185 (comment), I'd much rather focus on consistent repair techniques across browsers than kicking the can down the road to AT, where the behaviour is a lot less likely to be consistent. Doing that is essentially a hack: we don't want to specify it here, so we let AT sort it out, since there are less stringent requirements on AT. There are already enough complaints about inconsistency across AT. This is only going to make it worse.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should chat about this one. The platforms that stand out are Mac/iOS, which don't provide a means of marking a node hidden. They've discussed it in the past and wish to remain an outlier rather than make this change, which would also involve changing their ATs. @cookiecrook, given James' concerns, is there a chance to reconsider?

Jamie, I was leaning toward it being a UA choice -- but if Firefox were to do the same thing, I suppose we could make it a platform choice based on availability of the hidden state. For Chrome, we would like to mark hidden on all platforms other than Apple platforms right now, because this gives us the best experience everywhere. This is what we think is best for our users. I understand what you're saying about interop, but on platforms that already have a hidden state, ATs already that by skipping those nodes just like the obviously must do for nodes that are there. But maybe you have a specific example of what might not be good for interop — I'd be interested to know if the problem created is bigger than the improvement we get by allowing ATs to do repairs on bad content.

I don't think hit testing should return hidden nodes.

Regarding responsibility of repairs all being on the UA, we don't agree about this. I think we should pass some responsibility to ATs, who are more nimble and better understand their users and use cases, and can apply more logic. ATs are never going to work exactly the same. So I guess we just disagree on this point. @ggordon-vispero has convinced me that they should have a chance at repairing content when their phone lines are lighting up about it, and they can't get traction anywhere else. Not every user is going to run with a bunch of bookmarklets or extensions to fix things.

Copy link

Choose a reason for hiding this comment

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

Making it a UA choice is a huge interop issue. Certain things (like how we render video controls or a date picker) are reasonably UA choices because they don't have a material impact on what a user can access that the site provides, even if a user might prefer one over the other. The different behaviour is surprising and confusing for both authors and users.

Especially having done this from both sides, I do understand the pragmatic need for repair, but we should still be doing our utmost to maximise interop here. Having significant differences in terms of what a user can access depending on what combination of software they use in multiple different dimensions doesn't really help anyone. This is why I'm more in favour of browser repairs here. That said, if we can get agreement that this is desirable across the board, we don't have inconsistencies across platforms and we iron out hit testing behaviour, etc., I can live with this.

If hit testing doesn't return hidden nodes, that solves one problem - an AT can easily get to visible content - but it also creates another. Your AT repair use case is now biased towards screen reader users who use a keyboard, where hit testing doesn't matter much. What about low vision screen reader users who use a touch screen or mouse? There's no way they could ever access this hidden content, repair or not, because explore by touch and mouse tracking require hit testing. The AT can't even work around that because the AT has no idea of visual layers created by z-order, stacking contexts, etc.

Finally, do Android and UI Automation have a way to expose hidden nodes in the tree? I didn't think they did.

Copy link
Contributor

Choose a reason for hiding this comment

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

UI Automation only has a hidden text attribute, looks like: UIA_IsHiddenAttributeId. Possibly not enough, I'll have to look into it.

I should note Chrome actually already uses STATE_SYSTEM_INVISIBLE and hidden:true on focusable nodes inside of aria-hidden, and has done so for a while. I'm not aware of it causing interoperability issues. We don't expose any other nodes in aria-hidden subtrees currently. If there are demonstrable issues with doing it then we would reconsider this part of the proposal.

The reason I like this mitigation overall is that the other mitigations aren't enough, say if the user is visiting via virtual buffer and not focusing the aria-hidden content, aria-hidden=false wasn't used, and it wasn't on the body or html. Those other mitigations are great but it won't get everything, and there are some mitigations that aren't appropriate in the browser. For example, some screen readers probably want to allow scripts or settings files to add aria-hidden content back to the virtual buffer by listing specific bad urls. I don't think it's a big interoperability issue so much, because the default is still to not put the aria-hidden content into the virtual buffer. So the default will essentially the same between screen readers -- to not present it. It's true I'm most concerned about screen readers and I'm glad for your help to find potential issues in other ATs.

@ggordon-vispero would appeciate your thoughts on potential uses for the aria-hidden nodes if you are willing to share, and on any potential interoperability issues.

<p>Authors MAY use <code>aria-hidden="false"</code> within an <code>aria-hidden="true"</code> subtree to unhide the visibly rendered content within the <code>aria-hidden="false"</code> subtree.</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is a good idea. It has become incredibly common for devs to put things like aria-hidden={!open} in their code. Just look at a quick search for aria-hidden on GitHub:
https://github.com/search?q=aria-hidden%3D%7B+language:JavaScript&type=code&l=JavaScript

It is a rarity that devs set aria-hidden to null. That doesn't suggest to me that it is a common misunderstanding that putting aria-hidden="false" in an element with aria-hidden="true" will unhide the element. This is relied on a in a lot of places. Now not all of these might be real problems, since CSS might be at play there too, but assuming even 10% of those cases actually required aria-hidden, that is a lot of components that break because they can no longer be hidden with aria-hidden="true".

Very strong 👎 from me on this change.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think if you wanted an option to unhide with aria-hidden, a new value should be introduced like aria-hidden="reset", something explicit like that. Even done that way I think is very worrying. While that does add a new feature that may have some uses, it also removes the capability for a node to completely hide itself with aria-hidden="true". There is also some worrying interaction with inert, which cannot be unset and may result in inactive components that are still in the accessibility tree.

Copy link
Contributor

@aleventhal aleventhal Sep 18, 2023

Choose a reason for hiding this comment

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

This would only have any affect in a subtree that's both already aria-hidden="true" (marked on an ancestor) and visible to sighted users. Does that help?

We want to do this because some of the cases where we've seen inaccessible content has this pattern. The author didn't understand that aria-hidden="false" didn't do this.

Copy link
Contributor

Choose a reason for hiding this comment

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

This would only have any affect in a subtree that's both already aria-hidden="true" (marked on an ancestor) and visible to sighted users. Does that help?

That's how most accessible modals work: aria-hidden="true" on the container wrapping all non-modal content. It is essential for modals that aria-hidden="true" hides everything, and that stuff doesn't "leak" out because a dev on some third party component library just happened to put aria-hidden={!open} instead of aria-hidden={!open ? true : null}.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @WilcoFiers, this change would have potentially huge consequences — I personally would need to go have a mild panic about all the places this could potentially break our UI 😅.

The caveat that this would only apply to nodes that are visible to sighted users doesn’t help at all — all our modal background content is still technically visible.

<p class="note">This is a change from ARIA 1.2 and earlier versions: the spec has never previously explicitly allowed aria-hidden="false" to unhide anything, visually rendered or otherwise. Additionally, it also differs from user agent implementations in several ways: 1) <code>aria-hidden="false"</code> no longer un-hides content that is also hidden in CSS, as there are other techniques to present content only to AT users that works more consistently (e.g. positioning offscreen), and 2) <code>aria-hidden="false"</code> previously was a <code>noop</code> on (e.g., did not affect) visible content, and 3) <code>aria-hidden="false"</code> previously operated only on a single element. However, the <code>aria-hidden="false"</code> behavior of previous versions was never consistently implemented, understood or commonly used.</p>
<p>Authors MUST prevent elements from receiving focus within aria-hidden content because when users reach this content, the AT can provide the user with no information about the content; this leads to a disorienting and inaccessible experience. User agents MUST repair this situation by ignoring <code>aria-hidden</code> on the ancestor of the focused element. When this repair is made, user agents SHOULD inform authors through developer tools (if available).</p>
<p>Authors MUST NOT use <code>aria-hidden="true"</code> on the document element or body of the main document (e.g., <code>window.top</code>), where that would hide the entire accessibility tree. User agents MUST repair this situation by ignoring <code>aria-hidden="true"</code> when it would prevent all visible content from being available to ATs. Again, when this repair is made, user agents SHOULD inform authors through developer tools (if available). Notably, this situation does not occur when <code>aria-hidden="false"</code> is provided on a visible descendant, e.g. on a <code>role="dialog"</code>element, because that would generate some accessible content.</p>
Copy link

Choose a reason for hiding this comment

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

This raises a couple of mutation edge cases that I think need to be clearly specified:

  1. aria-hidden="true" is set on the document, aria-hidden="false" is set only on a descendant dialog. Later, the dialog is removed. Should the UA now ignore aria-hidden="true" on the document, even though it wasn't ignoring it before?
  2. aria-hidden="true" is set on the document, no descendants have aria-hidden="false". The UA ignores aria-hidden on the document. Later, a diaog with aria-hidden="false" is added. Should the UA now stop ignoring aria-hidden on the document so that anything other than the dialog disappears?

If the answer to both of these is "yes", we might be able to cover that with a single sentence like "This repair should be re-evaluated whenever aria-hidden="false" descendants are added or removed from the document."

Copy link
Contributor

Choose a reason for hiding this comment

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

Jamie, for #1, I would say that this is a realistic scenario that occurs. Basically, the author is trying to do a modal dialog and forgets to remove aria-hidden on the document. Since it's a realistic scenario, I think we should ask UAs to track that scenario and now ignore the aria-hidden.

For #2, once any aria-hidden is ignored on a given node for any of the repair reasons, it can just be ignored from there on out. Personally I think that makes implementation simpler in that we can keep a list of bad aria-hidden roots. Also, once a repair occurs, a warning of some kind should be emitted through developer tools -- this could take a number of different forms, where a console warning is the simplest.

Copy link

Choose a reason for hiding this comment

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

Fair enough. In that case, the spec wording needs to be clarified accordingly. Something like this:

User agents MUST repair this situation by ignoring <code>aria-hidden="true"</code> when it would prevent all visible content from being available to ATs. This repair should be re-evaluated whenever a descendant with <code>aria-hidden="false"></code> is removed. Once <code>aria-hidden="true"></code> is ignored on an element, it should remain ignored henceforth, regardless of changes to descendants.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another consideration -- what if the ignored aria-hidden attribute is removed, then re-added to the same node? Does it continue to be ignored, or does that reset it?

<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. 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…").</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could improve this paragraph. I find it cumbersome to understand everything but the first 7 words.

Authors are advised to use extreme caution with aria-hidden. Misuse easily and often leads to inaccessible content. Starting with ARIA 1.3, several mitigations have been added to reduce the likelihood of this. However, aria-hidden is too powerful to make any mistakes with: many users have no way to perceive content that uses it, and therefore authors MUST avoid using aria-hidden on any content required for successful user journeys.

Copy link

@tranjocelyn tranjocelyn Sep 14, 2023

Choose a reason for hiding this comment

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

I actually liked the examples here! Would like them to be kept if possible.

<p class="note">At the time of this writing, <code><sref>aria-hidden</sref>="false"</code> is known to work inconsistently in browsers. As future implementations improve, use caution and test thoroughly before relying on this approach.</p>
</div>
Expand Down
Loading