Open JAWS-test opened 3 years ago
Why not treat it like when having a display: table-cell
with a display: block
parent, if that's not broken? But probably that's also broken, which would hint that this isn't actually a visibility
problem.
visibility
was designed as an inherited property with the ability to be overridden in descendants. IMO the statement that you are proposing doesn't seem enforceable nor web compatible.
@Loirooriol
Why not treat it like when having a ...
In my opinion, such a warning should be placed with every problematic CSS property
display: table-cell with a display: block parent, if that's not broken?
No. In no browser that I know of, nor according to the specification, does display have any effect on the Accessibility API tree (except, of course, display:none). I.e. neither display:block turns a table into a non-table nor display:table-cell turns a non-table element into a table cell. There is only Chrome's decision to submit a table, fully created with all table property of CSS, as a layout table to the Accessibility API. However, this is firstly not a data table and secondly the tree is not broken by this, since it is submitted as a full table. Furthermore, I think this is a bug of the browser (https://github.com/FreedomScientific/VFO-standards-support/issues/344).
Furthermore, there are problems with display:contents at tables. However, these are also browser bugs that have been fixed for example by Chrome in the meantime (https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html). But even these bugs never caused the Accessbility API tree to be broken, they only caused a table to not be output as a table. The tree itself was correct in itself.
Proposal:
visibility: hidden
similar to display: contents
(shouldn't strip necessary a11y information from the tree).The CSS Working Group just discussed visibility: visible and a11y
, and agreed to the following:
RESOLVED: accept proposal
OK, we've edited this into css-display-3 in https://github.com/w3c/csswg-drafts/commit/7be289b94ff79ca434d9fec0a107027f38088380
collapse
to include collapsed flex items as well as tables, and to allow additional special behavior to be defined in other layout specs.The new section can be seen at https://drafts.csswg.org/css-display-3/#visibility
We'd appreciate a review, and any suggestions for improved clarity! These edits will then go into the next CR publication.
If "their semantic role as a container is [truly] not affected, to ensure that any visible descendants are properly interpreted", then the note about the table cells shouldn't really apply, should it? Or is that only about legacy screen readers that aren't keeping the semantic role of the hidden parent elements?
Also: the note could really use some commas. I had a hard time navigating that first sentence of the note.
Also: I think it isn't just parent relationships that need to be considered. A label
element with a for
attribute should also retain its semantics when invisible, for example, IMHO.
However, as with display: contents, their semantic role as a container is not affected, to ensure that any visible descendants are properly interpreted.
Like @bradkemper, I don't understand this: either the role of invisible elements is preserved or not. If it is preserved, the accessibility warning is not needed.
However, it is currently the case that in the browsers I have tested, the role of invisible elements is not preserved, so the screen reader does not output the visible child elements with their correct role either. In Chrome, for example, this leads to the strange behavior that table cells are output, but no table, and no table navigation is possible. See https://codepen.io/jaws-test/pen/eYWXypB
I understand the use case here. However, I worry there are a lot of edge cases where the expected behaviour is now unclear. Unlike display: contents, visibility: hidden affects descendants. That raises a lot of questions about what should remain in the tree, what shouldn't and what should happen during certain mutations:
Let's look at some test cases:
1:
data:text/html,<p style="visibility: hidden;"><button>hello</button> <button style="visibility: visible;">world
Should hello (or its containing button) be exposed?
2:
data:text/html,<p style="visibility: hidden;"><button>hello <img style="visibility: visible;" src="https://via.placeholder.com/10x10.png" alt="messed up"></button> <button style="visibility: visible;">world
The "messed up" image should certainly be in the tree, but what about its parent button? The button has semantic relevance, yet you can't focus it or activate it, so exposing it seems wrong.
3:
data:text/html,<table style="visibility: hidden;"><tr id="tr" style="visibility: visible;"><th>hello <button onclick="tr.style.visibility = 'hidden'">broken world</button></th></tr></table>
When the tree is initially rendered, the new rule says the table should be exposed. But what happens when you press the button? Does the whole table get removed from the tree or just the row?
4:
data:text/html,<table style="visibility: hidden;"><tr id="tr" aria-hidden="true" style="visibility: visible;"><th>hello <button onclick="tr.removeAttribute('aria-hidden');">broken world</button></th></tr></table>
The new rule says the table should be exposed because of the visible child. But the visible child is aria-hidden. So should the table be exposed or not? And when you press the button and aria-hidden gets removed, does the table magically spring into existence?
These are just the cases I was able to come up with off the top of my head. I fear there are a lot more.
OK, we clarified the note. It now reads:
Note: Currently, many user agents and/or accessibility tools don't correctly implement the accessibility implications of visible elements with semantic relationships to invisible elements, so, for example, making parent elements with special roles (such as table rows) invisible while leaving child elements with special roles (such as table cells) visible can be problematic for users of those tools. Authors should avoid creating these situations until the tooling situation improves.
@jcsteh
For case 1, no, neither “hello” nor its containing button should be exposed, because they are hidden.
For case 2, the presence of the image should imply the presence of its containing button, allowing it to be focused and activated.
For case 3, CSS is stateless, so the entire tree is hidden just as if it were originally that way.
For case 4, I think this gets even deeper into undefined territory: afaik there isn't a clear mapping spec for the implications of CSS on the accessibility tree (and it's out of scope for css-display), but effects of visibility and aria-hidden should probably combine to hide the entire tree there. And again, the document is stateless so scripting the addition or removal of the attribute should be no different than loading a fresh document in that state. (It may be trickier to implement, though.)
Note: All of these should be identical to the same situations with display: contents
, so the answers should be the same as for that (or are equally undefined currently...)
I'm not sure I agree with that being the best thing to do for case 2, though I guess it fits with the current text. It seems a little weird and unexpected to me.
I think instead of "their semantic role as a container is not affected", it should be "their semantic role in relation to other elements is not affected". Thus, a hidden button would not have a semantic role of its own, even with a visible child. But a TD
would still reference a hidden TH
in a hidden THEAD
, and behave as though its parent table parts are not hidden, for the sake of giving meaning to the TD
(almost like how table fix up occurs when a parent table part is missing, but referencing the actual structures present instead of creating new ones). And aria-labelledby
would still work to get that label even if the thing it was labeled by was visibility: hidden
(but not if it was display:none
). But if the table-row had visibility: hidden
and tabindex="0"
, you still wouldn't be able to focus it, because it is hidden.
For aria-hidden, my understanding is that it is similar to display none
, in that it completely removes it and its children from the accessibility tree (as completely as display none
removes it from the render box tree). So visibility property shouldn't matter at that point.
The note part is better than the definition part, because the note speaks to "the accessibility implications of visible elements with semantic relationships to invisible elements". That is better than implying that all semantics, focusability, etc. of an element are retained if it has a visible descendant.
A text node child of a hidden element should also remain invisible, even if there are other visible descendants.
@fantasai
Note: Currently, many user agents and/or accessibility tools don't correctly implement the accessibility implications of visible elements with semantic relationships to invisible elements, ...
I do not think this phrasing is correct. If I had thought that the CSS specification was correct and only the browsers or the screen readers were doing it wrong, I would have posted a ticket for the browsers or the screen readers. However, I think the problem is in the CSS specification and that's why I opened the ticket here. How should the browsers and screen readers correctly handle visible content within invisible content? There is no good solution for this and that's why I would actually be in favor of abolishing the possibility of nesting visible content within invisible content. However, this is probably not possible and therefore there should be an appropriate warning, but not blaming the browsers or screen readers.
@JAWS-test
How should the browsers and screen readers correctly handle visible content within invisible content? There is no good solution
I think this spec is making good strides towards specifying reasonable solutions that might have been under specified before. But maybe the note could be reworded a bit, to eliminate the blaming tone. How about:
Note: Currently, many user agents and/or accessibility tools don't maintain the semantic relationship/connection of visible elements with invisible elements, ...
@bradkemper
I think this spec is making good strides towards specifying reasonable solutions that might have been under specified before
Unfortunately, I cannot agree with this. I think that for accessibility, for technical reasons, no good solution exists for visible elements nested within invisible elements. I don't know what such a solution should look like. And the current version of the specification doesn't offer a solution either. That's why I don't think user agents or assistive technology can fix the problem, which is one of the CSS specification, so I still think your softened suggestion is wrong.
But I don't want to argue further about the correct wording. The most important thing for me is that a corresponding warning is included in the specification. If this is achieved, I am satisfied
I don't feel like it is conceptually all that complicated. The real problem, in my mind, is that AT apparently treats visibility: hidden
and display: none
as though they mean the same thing, when they were never meant to be in CSS.
With display: none
, it effectively removes the element from the tree (the render tree and a11y tree), thereby also removing all its descendent branches with it.
But visibility: hidden
isn't supposed to do that. It is only making that one element or pseudo-element (including its text nodes) completely transparent, without affecting descendant elements except through inheritance. It still abides in the render tree, and should also still abide in the a11y tree. So, in my mind, the equivalent for screen readers is that it shouldn't be read aloud, but it should still maintain its meaning and structure and place in the tree. It should still directly traversable. And, because it is still tree-abiding, it should also be available for giving meaning to related table structures, or to providing labels through for
and aria-lablelledby
, or to providing description via aria-describedby
. If the element had no visible descendants and no descendants that acted as labels or table headers, etc., then it could be treated the same as display: none
or aria-hidden="true"
, because only then is the element and its descendants both empty and meaningless.
Is that not a reasonable way to look at it?
@bradkemper
Your ideas sound good, unfortunately according to specification HTML AAM display:none, hidden, visbility:hidden and aria-hidden=true is equal
@bradkemper
Please note that with respect to aria-labelledby and aria-describedby, visibility:hidden does not behave differently from display:none or hidden, i.e. invisible elements can be used as labels or descriptions as long as they are referenced, see: https://www.w3.org/TR/accname-1.1/
Your ideas sound good, unfortunately according to specification HTML AAM display:none, hidden, visbility:hidden and aria-hidden=true is equal
Yeah, but it shouldn't be, in my view, as that is contrary to how visibility has always been defined in CSS. Working Drafts can be changed. Can AT, in a way that doesn't disrupt existing pages, by still hiding branches in which all descendants are visibility: hidden
?
@bradkemper
My suggestion would be: If the CSS specification assumes that visibility:hidden only hides the content but leaves the semantic structure (roles) untouched, then a corresponding ticket should be posted at Accname, HTML AAM and ARIA by those who hold this view.
Related: https://github.com/w3c/aria/issues/1055 and https://github.com/w3c/accname/issues/57
Please note that with respect to aria-labelledby and aria-describedby, visibility:hidden does not behave differently from display:none or hidden, i.e. invisible elements can be used as labels or descriptions as long as they are referenced, see: https://www.w3.org/TR/accname-1.1/
Ah, I stand corrected on that point at least then. My (possibly faulty) remembrance of https://github.com/w3c/aria-practices/issues/1136#issuecomment-524026576 was making me think otherwise. Or is aria-hidden different from display:none in that regard? Anyway, that issue is 2 years old too, so things may have changed since then.
I don't feel like it is conceptually all that complicated. The real problem, in my mind, is that AT apparently treats
This isn't just AT. It's right throughout the entire stack: OS APIs, a11y specs, browsers, AT.
visibility: hidden
anddisplay: none
as though they mean the same thing, when they were never meant to be in CSS.
Maybe they're not the same in CSS, but CSS is also not primarily focused on semantics. So, a11y had to come up with a semantically appropriate mapping. That mapping has been this way for decades.
But
visibility: hidden
isn't supposed to do that. It is only making that one element or pseudo-element (including its text nodes) completely transparent, without affecting descendant elements except through inheritance.
The fact that it is inherited is hugely relevant for a11y. That means an entire subtree could be "invisible" to sighted users. Sites in the wild (e.g. Google Help Panel) use visibility: hidden to hide entire sections of a page from sighted users.
It still abides in the render tree, and should also still abide in the a11y tree. So, in my mind, the equivalent for screen readers is that it shouldn't be read aloud, but it should still maintain its meaning and structure and place in the tree.
But the a11y tree isn't the render tree. It does not have a concept of "an entire subtree which is in the tree but shouldn't be read". Furthermore, "shouldn't be read aloud" and "should still maintain its meaning" are incompatible concepts for a11y. If it has meaning, it should be read. Otherwise, it shouldn't be exposed. Again, this is how a11y works and has always worked. You can argue that's wrong, but unless we have a path to change an entire industry and stack, that's not a pragmatic argument.
If the element had no visible descendants and no descendants that acted as labels or table headers, etc., then it could be treated the same as
display: none
oraria-hidden="true"
, because only then is the element and its descendants both empty and meaningless.
And herein lies the problem. The proposal here requires that whenever a visibility: hidden element is encountered, browser a11y engines must do one of three things:
To make things even more complicated, we also have to figure out how to handle mutations. If a visible descendant gets added or removed, we have to do the whole calculation again, potentially rebuilding or throwing away massive parts of the tree.
At least for Firefox (speaking as the tech lead for Firefox a11y), any of these solutions is a massive undertaking and involves significant risk of potentially catastrophic regressions.
Note: All of these should be identical to the same situations with
display: contents
, so the answers should be the same as for that (or are equally undefined currently...)
Perhaps visibility: hidden and display: contents are conceptually similar in terms of layout implementation, but they are very different semantically. display: contents is a very clear message: this is semantically relevant but not visually relevant. In contrast, visibility: hidden is often used to hide entire parts of a page visually, so it is not necessarily saying "this is semantically relevant". It depends on the subtree, which is where things get really nasty as outlined above.
@bradkemper
Or is aria-hidden different from display:none in that regard?
I don't know exactly myself, because on the one hand it's very complex and on the other hand it's currently being updated
@JAWS-test Your original comment was about visible descendants of invisible ones being problematic for AT if the invisible ones had important relationships to the visible descendants. Your latest comment seems to imply that any visible descendant of an invisible one is problematic for AT. That's a much bigger scope of “things that must not be done” than you originally asked for, why the scope change?
@fantasai Unfortunately, this is a misunderstanding. In most cases, visible content within invisible content is not a problem. Only in a few cases, as mentioned in my first comment, it is problematic, namely exactly when nested elements are used that need each other mandatory (like table, tr, th)
they are very different semantically. display: contents is a very clear message: this is semantically relevant but not visually relevant. In contrast, visibility: hidden is often used to hide entire parts of a page visually, so it is not necessarily saying "this is semantically relevant".
I would say that there can still be probably more similarity than fundamental difference between these situations. Authors (at least those who care about semantics) don't often use CSS only to mark parts of content "semantically not relevant". In my opinion, the true message behind display: contents
is usually "this particular thing may be not visually relevant, but its children definitely are"; the message behind making a thing not visible while still being part of layout and probably having visible descendants sounds more like "this particular thing may be not visually relevant (now), but some of its descendants probably are". If an educated author's intent is to mark the entire piece of the page as both visually and semantic not relevant, they would likely use display:hidden
display:none
or the HTML hidden
attribute instead.
That said, I agree that accurate repesentation of this implied message is difficult. Moreover, difficuilties can be different in these cases related to different layout limitation. For example, a table with visibility: hidden
that has several visibility: visible
cells still looks much like a table (though some its cells are missing), so, as an AT user, ideally I would like it to be presented like "Table, N rows, M columns, only X cells are visible. Row A, column B: ..." and so on, and also would like to be able to navigate between visible cells using table navigation keys the same way as it happens in the table with some cells merged (I don't really expect it to be ever implemented that way, it's just my dream behavior 😀). In contrast, if the rows of the table have display: contens
and their cells are displayed as a single row, it would likely be better to present them as a single row in the A11Y tree (and I'm not sure if it's possible without "fixing" the A11Y tree with "virtual" role="row"
wrappers similarly to how CSS "fixes" the layout tree with anonymous boxes).
I think there is a simple rule of thumb as to when visible content within invisible content is problematic: Remove all invisible content from the source code and leave all visible content in the source code. Check with a syntax checker whether the source code is valid:
Authors (at least those who care about semantics) don't often use CSS only to mark parts of content "semantically not relevant".
I mostly agree, except for display: none
, which is used to completely remove something that is not currently relevant, but may be later, when it is shown by giving it some other display type. For instance an empty dialogue structure that exists as hidden scaffolding, but not part of the layout until it is filled in with content and then shown.
On the other hand, there can also be cases like tool tips where they start out as display: none
until hovered, but could have meaning for AT even when not hovered (aria-describedby
would work well with those, if that worked when referencing display: none
elements).
In my opinion, the true message behind
display: contents
is usually "this particular thing may be not visually relevant, but its children definitely are";
Or it's text or picture data content. I guess those are children nodes but not children elements. But yes.
the message behind making a thing not visible while still being part of layout and probably having visible descendants sounds more like "this particular thing may be not visually relevant (now), but some of its descendants probably are".
Yes, sometimes, but not always.
If an educated author's intent is to mark the entire piece of the page as both visually and semantic not relevant, they would likely use
display:hidden
or the HTMLhidden
attribute instead.
I think you meant display: none
. I would agree.
That said, I agree that accurate repesentation of this implied message is difficult. Moreover, difficuilties can be different in these cases related to different layout limitation. For example, a table with
visibility: hidden
that has severalvisibility: visible
cells still looks much like a table (though some its cells are missing), so, as an AT user, ideally I would like it to be presented like "Table, N rows, M columns, only X cells are visible. Row A, column B: ..." and so on, and also would like to be able to navigate between visible cells using table navigation keys the same way as it happens in the table with some cells merged (I don't really expect it to be ever implemented that way, it's just my dream behavior 😀).
Yes, that seems like very reasonable behavior, from my perspective. (Disclosure: I am sighted and don't use AT myself except to test my pages for a11y.)
In contrast, if the rows of the table have
display: contens
and their cells are displayed as a single row, it would likely be better to present them as a single row in the A11Y tree (and I'm not sure if it's possible without "fixing" the A11Y tree with "virtual"role="row"
wrappers similarly to how CSS "fixes" the layout tree with anonymous boxes).
Agreed. That is what I was alluding to earlier when talking about visibility, that some sort of fix up could happen for a11y like it does for CSS. The same sort of fix up should happen if the table row was given some other role, like role="dialog"
, but the cells still had their cell role. This gets beyond just CSS, but seems like what WCAG and AT and UAs should be working towards, rather than just saying we have to keep it a certain way because it's been that way for decades.
I think there is a simple rule of thumb as to when visible content within invisible content is problematic:
Remove all invisible content from the source code and leave all visible content in the source code. Check with a syntax checker whether the source code is valid:
That makes more sense for display: none
than for visibility: hidden
. Or would if AT etc. we're interpreting visibility: hidden
in a way that was more closely equivalent to how it is used for visual display.
At least for Firefox (speaking as the tech lead for Firefox a11y), any of these solutions is a massive undertaking and involves significant risk of potentially catastrophic regressions.
I do not doubt you.
On the other hand, I would guess that most pages that use visibility: hidden
, or responsive tables with display values that change for narrow layouts and hide the theads
(none of which should change the semantics), and a lot of other things are just plain broken for a11y when (most) web authors don't take enough special care to try to work around some of the surprising things CSS can do to a11y. I don't mean to disparage your work, but it seems like there is a lot of opportunity to improvement for how CSS and a11y interact and intersect, and perhaps some of it could be done incrementally.
That is what I was alluding to earlier when talking about visibility, that some sort of fix up could happen for a11y like it does for CSS. The same sort of fix up should happen if the table row was given some other role, like
role="dialog"
, but the cells still had their cell role. This gets beyond just CSS, but seems like what WCAG and AT and UAs should be working towards,
This already happens for cases where a container accessible is given an incompatible role. For example, if you do this:
<table role="dialog"><tr><td>hi</td></tr></table>
Firefox doesn't expose row or cell.
The reason this case is much more complicated is that invisible things don't exist in the a11y tree at all. As I explained in https://github.com/w3c/csswg-drafts/issues/6123#issuecomment-918762792, "fixing" the tree in this case would require some pretty expensive crawling and book keeping.
rather than just saying we have to keep it a certain way because it's been that way for decades.
If I read you correctly, you're trivialising the concerns here, which seems pretty unreasonable to me. I'm not saying we should keep something the same way forever "just because". I'm saying that there is a great deal of implementation complexity and regression risk associated with this change (as well as unclear behaviour for various edge cases) that doesn't seem to have been taken into consideration.
https://drafts.csswg.org/css2/#visibility defines that I can make invisible areas (visibility:hidden) visible with visibility:visible.
The invisible areas do not show up in the browsers Accessibility API tree. The visible ones do. This causes certain nested elements to become invalid for the API (e.g. table cells without a table, because the table is marked with visibility:hidden, but the table cells are marked with visibility:visible).
I suggest to include a warning in the specification that visibility:visible must not be used with nested elements.
Otherwise, there will be a broken tree in the Accessibility API and incorrect output from screen readers