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

[css-display][css-aam][selectors-4] How elements with `display:contents` get focused? #2632

Open
SelenIT opened this Issue Apr 30, 2018 · 25 comments

Comments

Projects
None yet
8 participants
@SelenIT
Collaborator

SelenIT commented Apr 30, 2018

In #2355, we clarified that only display:none can affect the element's semantics and interactivity, so element with display: contents should preserve all its interactivity, including the ability to get the :focus (and :focus-visible) state. However, it's not clear how this state should work for this element.

Like the element with no boxes can be in :hover state (see #1141), making it possible to choose its descendants with selectors like .no-boxes-element:hover .something, I believe that the element that would receive focus without display: contents should receive it with display: contents as well. So its descendants should match the selectors like .no-boxes-element:focus .something, and its ancestors should match the :focus-within pseudo-class, making it possible to make the focus change visible despite the element itself is not displayed. And the fact that current browsers (at least, Firefox and Chrome) don't apply focus to the element with display:contents is the bug of these browsers. The element is still there in the DOM, it still can be activated, so there is no reason to skip the focus for it.

However, it's still not very clear how should the :focus state work for the element with display: contents itself. I suppose the easiest way to implement it is to only apply the :focus styles to the element's contents via inheritance. This would mean that the elements with display: contents would not get the default browser "focus ring", but the changes of color, background etc. would be inherited automatically by the element's descendants, and authors would be able to explicitly style the specific descendants of these invisible focused elements.

Another option can be to introduce a new pseudo-element like ::selection that would span all the visible contents of the invisible element and get the default browser focus decoration, so the focus would be always visible, but this approach looks more complicated.

@emilio

This comment has been minimized.

Collaborator

emilio commented Apr 30, 2018

It doesn't make much sense to focus something that doesn't have a box. :hover only works because it's applied to the whole parent chain. That is, comparing :hover and :focus isn't really appropriate. The comparison should be, I think, :hover and :focus-within, which should work with display: contents without issues AFAICT.

@AmeliaBR

This comment has been minimized.

AmeliaBR commented Apr 30, 2018

@emilio The intention of display: contents is that it should only affect layout, not functionality. So we need to consider how it applies to interactive elements, not just container elements.

For example, you might want to switch the appearance of a widget from a drop-down list on small screens to a radio-button style layout on larger screens, by making the container disappear and just laying out the options. However, you would still want the widget container (the element that replaces the role of a <select>) to gain focus (and then use arrows to switch between the options).

Or, you might have a link containing both an image and a text block. For layout purposes, you want to lay out the two parts as independent children of the parent layout context, but they are still a single link for accessibility and function.

(Note: current browser implementations of display: contents fail to live up to this promise. But that's another issue.)

The :focus selector would still match the focused element, so any inheritable changes set on it (like color) would propagate to the children. But as currently defined, and box-based styles on the element itself (like outline) would not apply. Which means that default browser focus styles, and most author-defined focus styles, would no longer be rendered.


I like the idea of extending the properties that apply to highlight pseudo-elements to also apply to display: contents elements. The current list of properties in that category are:

  • color
  • background-color
  • cursor
  • caret-color
  • outline and its longhands
  • text-decoration and its associated properties
  • text-emphasis-color
  • text-shadow

The only ones which apply in a way different from simply inheriting to child content are outline and background-color. Background color is well supported for selections, so should be straightforward to apply to a multi-box collection equivalent to a display: contents element. Outline would need to be implemented in a way that supports more than a simple rectangle, but this is already true for elements with absolute/fixed position children that extend outside of the parent.

@tabatkins

This comment has been minimized.

Member

tabatkins commented Apr 30, 2018

Whether or not an element is focused has no relation to whether it's in the box tree or not. (It might affect whether some methods of focus-traversal can reach it, but you can always .focus() the element.)

Whether or not the element is display:contents has no effect on how selectors match and properties apply; a focused element will always match :focus if it's focused, and the specified properties will be applied to it. None of them will have any direct effect on the element, because it doesn't generate a box, but inheritance will still work.

We shouldn't try to get extra-smart with this; the element is gone, there's nothing to apply a background or anything else to. (Note, for example, that if you display:contents a child of a grid container, its children are now grid items, and can be moved arbitrarily around the grid; there is nothing remotely related to the "first line" concept to refer to here.)

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented Apr 30, 2018

It doesn't make much sense to focus something that doesn't have a box.

Respectfully disagree. If the element is interactive, can get activated and responds to events as usual, and its activation behavior can have a visual effect on its contents, I see no reasons why this element can't get focus. Making the element and all its subtree completely unfocusable just because if is displayed differently (not hidden/removed!) is a real usability problem and very unlikely matches the authors' intent to just change the element's presentation, but not changing its semantics nor behavior. Having some "virtual" focus state for the invisible element that can make visual change through the element's descendants seems to be much less problematic than having the element and its whole visible subtree completely not focusable.

It might affect whether some methods of focus-traversal can reach it

@tabatkins, could you please show an example? The only way how CSS can prevent the visible and otherwise focusable element from getting focus through click/tap I can quickly imagine is pointer-events: none for all its children, but this seems unrelated to display to me. I agree with you that "we shouldn't try to get extra-smart with this", and the example with grid container's "grandchildren" displayed as separate grid items seems not more complicated to me than, e.g., absolutely positioned children of the visually hidden focused elements. CSS already can handle multiple boxes changing their style because of one element getting focused, Does display:contents really add anything new here?

@emilio

This comment has been minimized.

Collaborator

emilio commented May 1, 2018

Making the element and all its subtree completely unfocusable just because if is displayed differently (not hidden/removed!) is a real usability problem and very unlikely matches the authors' intent to just change the element's presentation, but not changing its

FWIW, I agree that making descendants not focusable is a bug. I haven't been able to reproduce it in FF Nighly / FF 58, but Chrome does indeed prevent you to tab to stuff in display: contents subtree. I'd file a Chrome bug for that nor Chrome.

(EDIT: I was going to file myself a Chrome bug and couldn't repro anymore, so probably I wasn't testing what I thought I was testing).

@emilio

This comment has been minimized.

Collaborator

emilio commented May 1, 2018

It's not clear to me that focusing elements without a box is great in general... It'd be confusing to have to tab twice to get to the <input> in:

<!doctype html>
<div style="display: contents" tabindex="0">
  <input>
  <div tabindex="0">Focused?</div>
</div>

Which UI should the browser show when the display: contents element is focused? Note that it has no box, it's descendants could be anywhere in the layout tree. It seems like a really weird behavior to explain to any user IMO... I could be convinced otherwise I guess.

In any case agreed that we should special-case as little as needed. Special cases = bugs.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented May 2, 2018

it has no box, it's descendants could be anywhere in the layout tree. It seems like a really weird behavior to explain to any user IMO...

Completely agree that it's weird. Exactly like so is the focus behavior of, e.g., an off-screen positioned element with on-screen absolutely positioned children, or the focus behavior of the element with good old outline:none and no replacement for it. Sure, it is bad. But, just like in case of outline:none, it can be fixed (by applying special styles to the element's descendants, in this case). Sure, this possibility (as many others in CSS) can have bad effect on the usability, and the spec probably should warn against misusing/overusing it.

But removing the element from the accessibility tree altogether without any possibility to bring its displayed parts back into it, as browsers currently seem to do, looks way more problematic to me, regardless how good the intent of this behavior was.

@tabatkins

This comment has been minimized.

Member

tabatkins commented May 2, 2018

@tabatkins, could you please show an example?

I imagine that display: none on an element prevents it from being tabbed to. display:contents would do the same thing - there's nothing on-screen to be tabbed to!

I agree with you that "we shouldn't try to get extra-smart with this", and the example with grid container's "grandchildren" displayed as separate grid items seems not more complicated to me than, e.g., absolutely positioned children of the visually hidden focused elements.

Note - I was responding to the idea that display:contents elements could still have background work on them, like ::first-line.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented May 3, 2018

To me, display: contents and display: none for the interactive element with visible content (e.g. button) are very different. While display: none really removes the element from the presentation, display: contents leaves its children on-screen, they still response to activation via click/tap, and the author most likely wants to preserve all the aspects of their interactivity. The interactive element with display:contents looks very similar to the common pattern for creating custom controls with native functionality: visually-hidden normal <input> + its sibling (usually a <label> or a <span> inside it) that adjusts its styling according to the state of the invisible <input>. The focus state is no different: the <input> elements gets focused by tabbing despite being off-screen, its visible siblings react to this and the user gets the decent UX.

From display:contents, I expect the behavior similar to that. I also expect that the primary use case for applying it to interactive elements would be similar — working around browser limitations of native control styling (e.g. inability to make the <button> non-atomic inline element) while preserving as much native semantics/functionality as possible. It's extremely unlikely that authors expect such elements, which are perfectly visible (through content) and reachable to users, to disappear from the accessibility tree and tabbing sequence.

P.S. Regarding background of these elements, I agree that they shouldn't have it themselves. What I suggested (as a kind of brainstorming-style option) was adding a new pseudo-element (e.g. ::now-focused), which in case of the regular element would match the element's box, and in case of display:contents element would be something ::selection-like that spans all the visible children of such element, and making browsers apply their default focus indication (outline etc.) to this pseudo-element instead of the element. But I admit that this option is not realistic:)

@tabatkins

This comment has been minimized.

Member

tabatkins commented May 3, 2018

That is not what display: contents does or what it's intended to do. It's meant to remove wrapper elements that are added for semantics or scripting purposes, but mess with styling that depends on direct parent/child relationships (like grid container -> grid item).

This is why it's not good for the case you're talking about. For example, as far as CSS is concerned, button is just a specially-styled span; a <button>foo</button> with display:contents is just raw text, not a button at all anymore. (You can still click on the text, and the click event will bubble up to the button element and trigger a button press, but that's because the DOM and the visual display don't have to have any particular correspondence with each other.) The text is not focusable by tabbing in a normal button, and it doesn't magically become tabbable here either.

The input+label example you bring up doesn't apply either; the input is offscreen, but still present in the page, so it can still be tabbed to and focused by user interaction just fine. You just can't click on it because it's not on screen; this is no different from it just being obscured by another element. This is very different from the element not existing in the rendering structure at all.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented May 4, 2018

Well, display:contents might be not intended for this, but the problem of styling limitations of native controls still exist and any option to strip off all the "dark magic" from these elements is too tempting. Until browsers offer the better way for this (some new appearance value?), authors would likely use all the available valid CSS means to solve this problem — including display:contents.

I'm OK with button:focus { display:contents } not being highlighted, and with its plain text content not highlighted as well. But I'm not OK with the span inside this button not applying the button:focus > span styles. And I'm very not OK with visible and interactive element that can't be keyboard-activated at all just because of its styling. As an author, I see the focus state as a property of the DOM element, not of a box in a render tree. It's the DOM element, not the box, that fires focus and blur events. And if something is interactive, it's expected to be focusable.

After all, in #2355 we added the following text:

Aside from the none value, which also affects the aural/speech output [CSS-SPEECH-1] and interactivity of an element and its descendants, the display property only affects visual layout: its purpose is to allow designers freedom to change the layout behavior of an element without affecting the underlying document semantics.

Doesn't this imply that the interactive element with no box but with visible children is just the element that can't be styled on focus itself, not the element that magically became not focusable?

@bradkemper

This comment has been minimized.

bradkemper commented May 25, 2018

And I'm very not OK with visible and interactive element that can't be keyboard-activated at all just because of its styling. As an author, I see the focus state as a property of the DOM element, not of a box in a render tree.

Agreed. I would expect the element to be in a focused state, but just not having any visual indication of that state, unless the author picked a descendant element to show an outline (or other distinctive styling).

@FremyCompany

This comment has been minimized.

Contributor

FremyCompany commented May 30, 2018

I think outline should work on display contents elements. Just like it works if an online gets split into lines or bidi or even regions or by a block-in-inline. I think the element should be focusable and tab-focusable too, but only if it is not replaced or a shadow host or none of its descendants in the light tree produce a box. The elements in cases noted above behave as if they were display none and should therefore not be focusable. Besides outline and a few other properties like pointer-event, properties should have no effect on the elements that do generate boxes for their descendants, besides being inherited by default.

@tabatkins

This comment has been minimized.

Member

tabatkins commented May 31, 2018

An inline getting split across lines is still generating fragments - focusing doesn't need to care whether there are 1 or more fragments, it works similarly in either case.

But zero fragments is a very different case. Where is that outline supposed to go??? There is zero promise of content contiguity here; if you display: contents a child of a grid container, the grandchildren can get placed willy-nilly in the grid, while the child has no position at all.

A display: contents element isn't visible. It isn't interactive. It's nothing at all. It just has contents that might be visible and/or interactive.

@FremyCompany

This comment has been minimized.

Contributor

FremyCompany commented Jun 1, 2018

I think a case can be made here.

Outlines may be non-rectangular. For example, if the element is broken across several lines, the outline should be an outline or minimum set of outlines that encloses all the element’s boxes.

The parts of the outline are not required to be rectangular. To the extent that the outline follows the border edge, it should follow the border-radius curve.

The position of the outline may be affected by descendant boxes.

@FremyCompany

This comment has been minimized.

Contributor

FremyCompany commented Jun 1, 2018

Now to the question "does the spec say you should have an outline in that case" I'd say probably no. Whether it should say so is another question. You will have to answer these questions anyway when working on exposing these elements to assistive technologies. If these elements are there for semantic purposes, they must be exposed, and you will somehow need to synthetize something for them.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented Jun 1, 2018

A display: contents element isn't visible. It isn't interactive. It's nothing at all. It just has contents that might be visible and/or interactive.

I guess this is 100% true from the (current) implementer's perspective, but not from the user's. If something is nothing at all, it can't have any visible/interactive parts at all. If the user sees and interacts with something, it can't be nothing — it actually is the visible and interactive element that can only be partially hidden. Even if its main part is hidden, the one that gets the focus outline. That's why it's OK for me if such element can't be styled directly on focus — it's really nothing to be styled directly — but it's not OK to have no focus state for it (that I could use to style its visible descendants) at all.

But zero fragments is a very different case. Where is that outline supposed to go???

With all due respect, I can't agree with treating the visible part of the display: contents element as "zero fragments". I think of ::selection as the closest existing thing. At least, the text content of the display: contents element can be selected, and these non-zero selected fragments can be styled somehow. So I suggested a new pseudo-element (something like old Mozilla's ::-moz-focus-inner, maybe?) that would span all these fragments, so you would be able to apply the outline to it. But I could live without it, as long the element itself stays focusable in principle.

Maybe it would be better to make this pseudo-element proposal a separate issue, and focus on the focusability of the display: contents element itself here?

@tabatkins

This comment has been minimized.

Member

tabatkins commented Jun 1, 2018

If something is nothing at all, it can't have any visible/interactive parts at all.

Yes, and that is exactly the case here. There is absolutely no visible/interactive parts of the element on the screen. It's children are visible, but that's something entirely different. display:contents, insofar as possible, makes it so that the element was never in the page at all. (It exists so you can add wrappers for scripting or a11y purposes that aren't intended to show up in the page itself.)

As far as CSS is concerned, there is literally nothing there.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented Jun 1, 2018

Functionally, the element subtree is clearly the part of the element: it passes the events to the element, it inherits the CSS properties where possible, and it triggers the element's activation behavior. The only thing that display: contents changes (to literally nothing) is just one box in the box tree, the box of the element itself. But for the user, there is no difference between "clicking the button" and "clicking the button contents", as long as they result in the same action. This is already the case in current implementations. And it should be, because

the display property only affects visual layout

and not the interactivity of the element and its descendants (with the one and only exception for the none value).

In my opinion, we should find the way to preserve the states of the boxless active elements that the user can interact with trough their subtree. Although technically it's a new problem for CSS, functionally it reminds me much of the case of visible labels for off-screen controls I mentioned before.

@fantasai

This comment has been minimized.

Contributor

fantasai commented Nov 14, 2018

Wow, ok, so my conclusion about all this is the following:

  • Elements that have 'display: contents' can still be focused (e.g. through Element.focus()), and will be selected by the :focus selector, as usual. I think the specs are clear about this.
  • Whether a particular user interaction focuses the element is undefined and up to the UA and can depend on the type of interaction. However, to the extent that interacting with a descendant of the element would normally focus the element even if their respective boxes were disjoint, it should be focused.
  • Backgrounds and borders definitely do not apply to elements that have 'display: contents' as there is no box for them to apply to.
  • Whether 'outline' can apply to 'display: contents' elements is an interesting open question, because 'outline' is defined to be able to draw around descendants, not just the box itself. So the outline can exist without the element's own box existing. We should probably file this as a separate issue against css-ui×css-display.

Is there anything else I missed here?

@Loirooriol

This comment has been minimized.

Collaborator

Loirooriol commented Nov 15, 2018

Elements that have 'display: contents' can still be focused (e.g. through Element.focus())

@fantasai I'm not that sure about this. An element with display: contents generates no box, thus it's not being rendered, thus most conditions for being a focusable area don't apply. So the focusing steps used by focus() may be aborted.

@emilio

This comment has been minimized.

Collaborator

emilio commented Nov 16, 2018

Note whatwg/html#3947 too, regarding that :)

@emilio

This comment has been minimized.

Collaborator

emilio commented Nov 16, 2018

Though for focus() Gecko at least does abort when the element is display: contents.

@SelenIT

This comment has been minimized.

Collaborator

SelenIT commented Nov 16, 2018

An element with display: contents generates no box, thus it's not being rendered,

Respectfully disagree. "Doesn't generate a box" !== "doesn't have any associated CSS layout boxes, SVG layout boxes, or some equivalent". If, e.g., the ::before and ::after pseudo-elements boxes are not associated with the originating element, it would be super strange from the author's perspective. I'd argue that the text run generated by the element's bare text should probably also be considered "some equivalent" of the CSS box for that purpose. The definition of the "rendered element" in HTML clearly was created before display:contents was introduced, but at least I clearly read it as "if element has any visible parts", and I can't agree that the content of the element is not its part. Probably that HTML definition should be also clarified further?

@Loirooriol

This comment has been minimized.

Collaborator

Loirooriol commented Nov 16, 2018

the ::before and ::after pseudo-elements boxes [...] the text run generated by the element

@SelenIT My understanding is that the boxes generated by ::before and ::after are associated with the pseudo-element itself, and not directly with its originating element. And a text run is associated with the generating text nodes, and not directly with their parent element.

The definition of the "rendered element" in HTML clearly was created before display:contents was introduced

This is a fair point which should be addressed in @emilio's issue. But currently I don't think "the specs are clear about this" [an element with display: contents being focusable] as @fantasai says.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment