w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.51k stars 669 forks source link

[css-ui] outline is not visible when moving focus programmatically #4421

Open Yaffle opened 5 years ago

Yaffle commented 5 years ago

Hello,

Some web browsers on some platforms have started to hide the focus ring (outline) until a user uses TAB key. But it is needed to change the focus programmatically in response to a keydown event, there is no way to tell the browser to show the focus ring.

:focus {
  outline: <something>; /* no way to tell it to show the default outline: it will be non-default or hidden*/
}

My use case: https://jsfiddle.net/82fdoqL5/embedded/result/ My bug against Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1585955#c8 I was suggested to solve this by using an option to a "focus" method, but this needs a change in the HTML spec: https://github.com/whatwg/html/issues/5004

Can CSS provide something to help with this? Thank you

tabatkins commented 5 years ago

@alice

AmeliaBR commented 5 years ago

On the CSS side, there are two features related to your request:

  1. If you want to trigger the appearance of the browser's default focus ring, use outline-style: auto (or outline: auto). But, this sets the appearance any time the rule applies. If you wanted to recreate the browsers keyboard-only focus logic for your scripted focus control, you'd need to use your own code to decide when to apply this CSS rule or not, e.g. by adding a custom class in addition to setting focus.

  2. If you want to apply a custom appearance focus ring that only shows up when the browser's default focus indicator would show, use the new :focus-visible pseudoclass. It matches only for focus states where the browser would show an indicator, allowing you to change the keyboard focus style without applying the same style for all focus states.

CSS doesn't define when the focus ring should be visible: it only exposes the browser's default focus behavior as pseudoclasses. And you're right, the browsers don't have the same nuance for scripted focus control. I agree that's a limitation, but I don't think adding more CSS focus pseudoclasses would help.

There are two solutions I can see: either the browsers get smarter about paying attention to which event preceded the focus() method call (and using that to decide if :focus-visible applies) or they let authors handle it with a DOM method option. The problem with letting authors handle it is that there is a history of web developers hiding the focus ring even when it is necessary. At the very least, the default should be to show the focus outline for scripted focus & the option should be to say it isn't necessary.

Yaffle commented 5 years ago

@AmeliaBR , Thank you, for so informative reply.

:focus {
  outline: auto;
}

But, this thing does not work currently in Chrome (it does nothing) or Firefox (it draws solid outline). Is it a browser bug? Seems, not, because "auto" means to use the default behavior of drawing focus only when the browser thinks it should be drawn.

May be, CSS could provide a new property to force the browser to show the focus outline.

AmeliaBR commented 5 years ago

Ah, looks like CSS does define this, as "non-normative suggestions":

  • If the active element matches :focus-visible, and a script causes focus to move elsewhere, the newly focused element should match :focus-visible.

  • Conversely, if the active element does not match :focus-visible, and a script causes focus to move elsewhere, the newly focused element should not match :focus-visible.

It's that last point that is problematic. Example case: I click on a custom select box to focus it, then use the arrows to cycle through the options. The original focus of the box may not have required a focus ring, but the keyboard actions do need one.

I would therefore recommend removing that last bullet point from CSS UI, and beyond that I'd hope browsers would add some heuristics for identifying which user interaction caused the change in focus (as Alice mentions on the HTML issue).

AmeliaBR commented 5 years ago

"auto" means to use the default behavior of drawing focus only when the browser thinks it should be drawn.

Sorry if I was unclear, but that is incorrect.

outline: auto means draw an outline whenever this rule applies. Only the style of the outline is based on browser defaults (which may be solid or dotted). Also, I should have mentioned: most browsers also have a prefixed keyword for defining the default outline color, which isn't standardized yet (but maybe should be). But again, that is about what the outline looks like, not when it is shown. When it is shown is defined by :focus, :focus-visible, and any custom CSS classes you add.

alice commented 5 years ago

@AmeliaBR

It's that last point that is problematic. Example case: I click on a custom select box to focus it, then use the arrows to cycle through the options. The original focus of the box may not have required a focus ring, but the keyboard actions do need one.

The arrow key interaction should cause the custom select box to start matching :focus-visible, per the other heuristic:

if the most recent user interaction was via the keyboard; and the key pressed was either Tab, Shift + Tab, or an arrow key; then the modality is keyboard. Otherwise, the modality is not keyboard.

AmeliaBR commented 5 years ago

Hmm, OK, that works if you also apply that rule when doing a scripted focus change. I wasn't making the connection that those two go together.

Still doesn't address fully custom keyboard focus control, like the j/k navigation used in many social media feeds. But that's probably something you'd want to use more explicit classes for, instead of relying on :focus-visible, anyway.

alice commented 5 years ago

Good point. I would be happy to revisit the "safelist" approach here - I think we were trying to achieve something that might be better achieved with a "blocklist" along the lines of "don't match :focus-visible if a meta-key was pressed"

Yaffle commented 5 years ago

I am fine with the solution like:

[role="grid"] *:focus {
  outline-style: auto;
}

Should I open a bug/modify currently opened against Chrome and Firefox? https://jsfiddle.net/qgjwbh8y/