Open emilio opened 1 year ago
I agree this is an issue. We (really @dizhang168) are in the process of trying to prototype an implementation of reading-order-items
, and this case came up. For example:
<div style="display:flex; reading-order-items: flex-visual">
<div style="order:2">Flex item
<button>B</button>
<button>C</button>
</div>
<div style="display:contents" tabindex=0> Problem here
<div style="order:3">Flex item <button>D</button></div>
<div style="order:1">Flex item <button>A</button></div>
</div>
</div>
In this example, the sequential focus order should be A, B, C, D. There's no well defined "place" to put the display:contents
div, since a) it isn't a flex item that can be ordered according to the flex visual order, and b) it can wrap flex items that aren't even contiguous. There's a similar (and slightly more complicated) case for <slot>
s within a flex container like this.
I propose these display:contents
elements within a reading-order-items
container should simply be declared non-focusable entirely. Unless there's a compelling use case for doing that?
The case for <slot>
gets more complicated, since a <slot>
forms a focus navigation scope which must be traversed in its entirety before returning to the owning scope. We think we have a solution for that, maybe, but that's likely a good candidate for another issue. But it'd be simplified, here, by saying <slot>
(which is display:contents
by default) cannot itself be focusable within a reading-order-items
container.
I think that makes sense, if you have used display: contents
I don't see why you would also want to include it in the order. I'd be interested if anyone can think up an example of needing to do so.
A fieldset with display: contents and the form controls reordered inside or so, perhaps?
A fieldset with display: contents and the form controls reordered inside or so, perhaps?
That's a good use case generally, but I don't think (?) you'd want to be able to focus the fieldset itself in that example, would you?
On this topic, here are a few more tricky cases where having display: contents on a flex item causes unclear reading order.
<!DOCTYPE html>
<meta charset="utf-8">
<div>
<template shadowrootmode="open" shadowrootdelegatesfocus>
<style>
.wrapper {
display: flex;
reading-order-items: flex-visual;
}
</style>
<div class=wrapper>
<button id="A" style="order: 2">Item A</button>
<slot></slot>
<button id="C" style="order: 4">Item C</button>
</div>
</template>
<button id="B1" style="order: 1">Slotted B1</button>
<button id="B2" style="order: 3">Slotted B2</button>
</div>
Which will render to: Source order: A,B1,B2,C Visual order: B1,A,B2,C
Given the flattened tabindex-ordered focus navigation scope, step 2.2, we should visit all elements within a scope together (so B1,B2 together). However, that is visually the wrong order.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.wrapper {
display: flex;
reading-order-items: flex-visual;
}
</style>
<div class=wrapper>
<div style="display: contents" id="root">
<template shadowrootmode=open>
<slot></slot>
</template>
<button id="A2" style="order: 2">A</button>
<button id="B2" style="order: 1">B</button>
</div>
<button id="C" style="order: 3">C</button>
</div>
Which will render to Source order: A,B,C Visual order: B,A,C
Here, we have a DIV that is:
A reading order item is defined using layout information. And in this case, this DIV is invisible to the layout object. Should the DIV qualify as a reading order item? My thinking is yes or else we wouldn't be able to visit its content.
For more context on the points raised above about display: contents and slots, please refer to our HTML standards proposal. It has a suggested algorithm, visual examples and more details in the open questions about this specific problem. https://github.com/whatwg/html/issues/10407
To summarize, the questions are:
Potential resolutions:
The CSS Working Group just discussed [css-display-4] Define how reading-order / reading-order-items interact with focusable display: contents elements.
, and agreed to the following:
RESOLVED: reading-flow does not affect whether an element is focusable
To be clear, we didn't finish the discussion here, the resolution above is a partial conclusion that reduces the space of conclusions we could reach here.
In the meeting I said that I will comment in this issue expanding a bit more on my proposal and the nested interactive elements issues.
display: contents
, but is not focusable, then there are no issues, and its content should participate in the flow as usual, being affected by reading-flow
etc.display: contents
and is focusable, but does not have any other focusable elements inside, this element's content becomes one focusable area. Example: https://codepen.io/kizu/pen/rNgYzVW — currently, only Chrome allows us to focus this link, Firefox and Safari both ignore it, which is bad.If an element that has both display: contents
and is focusable has one or more other focusable elements inside, my proposal is to split the non-focusable areas (excluding whitespace text nodes) inside that element, making them distinct parts that participate in the tab order in the same way they participate in the reading-flow
.
By “Areas” I mean consecutive elements and text nodes. If there are no valid areas other than the nested focusable element, we first see if there is one with the whitespace and use it, otherwise we need to create an empty area at the beginning of that element and treat it as an absolutely positioned element in a static flow.
Example of the current state for this: https://codepen.io/kizu/pen/gOJXxgy — inner element can be focused by all browsers, but the parent with display: contents
is only accessible as a single area in Chrome, while Safari and Firefox ignore it.
The third case is the most complicated, but it is also a case which is already considered a bad practice: ideally, authors must not nest interactive/focusable elements inside each other. Some references for this:
Articles by @aardrian related to this:
A nested-interactive
rule in axe related to this:
While this is a bad practice, we need to guarantee that the users would still be able to access the content in at least some way.
Thanks for the summary! I have a couple of questions.
- Otherwise, if an element has both
display: contents
and is focusable, but does not have any other focusable elements inside, this element's content becomes one focusable area. Example: https://codepen.io/kizu/pen/rNgYzVW — currently, only Chrome allows us to focus this link, Firefox and Safari both ignore it, which is bad.
Note: this is only with the --experimental-web-platform-features flag turned on, which enables the DisplayContentsFocusable feature @dbaron is working on.
- If an element that has both
display: contents
and is focusable has one or more other focusable elements inside, my proposal is to split the non-focusable areas (excluding whitespace text nodes) inside that element, making them distinct parts that participate in the tab order in the same way they participate in thereading-flow
.
By "split" do you mean to find each consecutive subsequence of children and text nodes that are not focusable, and treat them as one focusable group? e.g. for the case of <div style="display:contents"><div>One</div><div tabindex=0>Two</div>Three</div>
it'd have three items that participate in the tab order, which would be visually indicated by the bounding rectangle of "One", "Two", and "Three"?
Also, how should we determine the order of them? should all of these dynamically split items be focus-ordered as if they had the same tabindex
as the display:contents
parent?
By “Areas” I mean consecutive elements and text nodes. If there are no valid areas other than the nested focusable element, we first see if there is one with the whitespace and use it, otherwise we need to create an empty area at the beginning of that element and treat it as an absolutely positioned element in a static flow.
What do you mean by "with the whitespace"?
Also, why "treat it as an absolutely positioned element"?
Example of the current state for this: https://codepen.io/kizu/pen/gOJXxgy — inner element can be focused by all browsers, but the parent with
display: contents
is only accessible as a single area in Chrome, while Safari and Firefox ignore it.
(note: also only with the Chromium flag I mentioned above)
Thanks for the notes! Indeed, I tested in the Canary with the flag turned on.
By "split" do you mean to find each consecutive subsequence of children and text nodes that are not focusable, and treat them as one focusable group? e.g. for the case of
<div style="display:contents"><div>One</div><div tabindex=0>Two</div>Three</div>
it'd have three items that participate in the tab order, which would be visually indicated by the bounding rectangle of "One", "Two", and "Three"?
Yes, this is correct. It is questionable how those should be exposed to the accessibility tree (I don't know what is the best answer: should we announce those with all the element's content? Only the “area”? Something else?), but from a user's perspective those could look like separate entities, and a user can expect to tab through them separately (but that's subjective, and different users could expect different things; but I don't think we need to have a control over this, given we are covering a misuse case).
Also, how should we determine the order of them? should all of these dynamically split items be focus-ordered as if they had the same
tabindex
as thedisplay:contents
parent?
Yes, if there is a tabindex
present on an element with display: contents
, all its “areas” should inherit it, but the other focusable elements inside should not (as it works now with the regular nested interactive elements: the parent with tabindex is separate, the children are kept in the normal tabindex order). If there is not tabindex
, then the tabbing order should follow the reading order (so, could also be controlled by reading-flow
).
What do you mean by "with the whitespace"?
<a>
<span tabindex="0"></span>
</a>
In the above code, there are two text nodes containing only whitespace: between <a>
and <span
and between </span>
and </a>
. I imagine that for a browser it would be easier to use one of those nodes as the focusable area.
Also, why "treat it as an absolutely positioned element"?
This is only for a case like <a><span tabindex="0"></span></a>
— there are no text nodes between the tags, but, ideally, we'd need to have something that could represent the focus state, ideally with a focus ring over it. As it is not a real element, we need to somehow position it, and given the flow can be anything, the least intrusive way to treat it could be to use the same “placeholder” absolutely positioned elements have in their “static” position. But maybe there are other, better, ways to handle this.
Hi @kizu! So, if I understand correctly, you are saying the behavior with feature DisplayContentsFocusable on in Chrome is what you expect.
If we look at the problem Mason shared:
<div style="display:flex; reading-order-items: flex-visual">
<div style="display:contents" tabindex=0> Problem here
<div style="order:3">Flex item <button>D</button></div>
<div style="order:1">Flex item <button>A</button></div>
</div>
</div>
Are you saying the order should be display: contents
-> A
-> D
?
Or should we be splitting it further into smaller focusable areas?
Yes, if there is a tabindex present on an element with display: contents, all its “areas” should inherit it, but the other focusable elements inside should not (as it works now with the regular nested interactive elements: the parent with tabindex is separate, the children are kept in the normal tabindex order). If there is not tabindex, then the tabbing order should follow the reading order (so, could also be controlled by reading-flow).
Could you give an example for this? Given a display contents DIV, you propose splitting it into multiple focusable areas and let them all have the same tabindex. Does it mean this DIV will get focused multiple times instead of just once during keyboard navigation? I feel like that might not be intuitive to the web users.
Also, would love your input on how this proposal would work with the changes I am proposing at https://github.com/whatwg/html/issues/10407. Thanks!
Side note, I have written and landed some tentative WPT tests for reading-flow here. Maybe this can help visualize different cases.
I have done more research and thinking about this open question. We came up with two main options on how to handle it.
display: contents
descendants of the container.We also recommend visiting all display: contents
elements after the sorted reading flow items.
We wrote out the HTML spec changes necessary and illustrated the approach with examples at https://github.com/whatwg/html/issues/10533.
@rachelandrew has also posted a blog post detailing the problem.
We recommend developers to give us feedback here or on the whatwg issue :)
The CSS Working Group just discussed [css-display-4] Define how reading-flow interacts with focusable display: contents elements.
, and agreed to the following:
RESOLVED: display:contents focusable element occurs immediately before its first child in visual order
The CSS Working Group just discussed [css-display-4] Define how reading-flow interacts with focusable display: contents elements.
.
https://github.com/w3c/csswg-drafts/issues/8589 / https://github.com/w3c/csswg-drafts/issues/7387#issuecomment-1640556638 propose new properties to alter the reading order of the items to follow the layout tree order, rather than the dom order.
However, that is in conflict with https://github.com/w3c/csswg-drafts/issues/2632 / https://github.com/mozilla/standards-positions/issues/772, in the sense that there doesn't seem to be a good order for
display: contents
elements that are focusable in a visually-order container.We need to sort out how these two proposals interact. What is the tab order of a
display: contents
element inside an element with non-auto
reading-order
?cc @rachelandrew @tabatkins @fantasai @dbaron