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

[selectors] :focus-visible matches on initial programmatic focus #5885

Open
mrego opened this issue Jan 21, 2021 · 24 comments
Open

[selectors] :focus-visible matches on initial programmatic focus #5885

mrego opened this issue Jan 21, 2021 · 24 comments
Labels
selectors-4 Current Work

Comments

@mrego
Copy link
Member

mrego commented Jan 21, 2021

We have a test focus-visible-010.html that checks that a programmatic focus on the load event, causes that the element getting focused matches :focus-visible. This test passes in the 2 implementations of :focus-visible (Chromium and Firefox).

However the spec doesn't mention anything about this in the suggestions list, and given that 2 browsers follow that, and we have a test, maybe it'd be nice to add that to the list too.

The spec mentions 2 cases of programmatic focus:

  • 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.

But it doesn't mention what happens when there's no active element before the programmatic focus. WDYT?

CC @alice @emilio

@alice
Copy link

alice commented Jan 21, 2021

Yeah, we probably should have included that in the heuristics listed in the spec when we added it in our implementation. I suspect I was just exhausted at the time.

I would support adding some language as the second bullet point (after the comment about user preferences) like:

By default, if there is no other signal to indicate whether focus should be made visible (such as a programmatic focus after page load, or focus triggered by the autofocus attribute), :focus-visible should match on the active element.

@mrego
Copy link
Member Author

mrego commented Jan 22, 2021

I think we should consider 2 cases:

  1. Script focus on load event or via autofocus.
<div id="target" tabindex="0">Target</div>
<script>
  window.addEventListener("load", () => target.focus());
</script>
  1. Script focus at any point when there's no current active element.
<div id="target" tabindex="0">Target</div>
<script>
  setTimeout(() => target.focus(), 1000);
</script>

I guess we want :focus-visible to match in both cases, at least that's what Chromium and Firefox do.

@alice
Copy link

alice commented Jan 24, 2021

Hm, what happens in the second case after a blur()? The code above is still directly after page load, it's just longer after a page load.

https://codepen.io/sundress/pen/WNGqobM tests the same thing, but after a user has interacted with the page, blurring the previously active element before the delay. In my testing, Chrome "remembers" the previous value for :focus-visible (i.e. matches when the "Press this button" button was pressed using the keyboard, doesn't match when it was pressed using a mouse). The same behaviour happens without the delay as well.

It seems that Firefox has not (yet?) implemented this suggested heuristic: "If the active element matches :focus-visible, and a script causes focus to move elsewhere, the newly focused element should match :focus-visible."

What do you think the behaviour should be for this case?

@emilio
Copy link
Collaborator

emilio commented Jan 25, 2021

Firefox implements that heuristic. But there's no active element since blur() was called, so that heuristic doesn't apply, what am I missing?

@emilio
Copy link
Collaborator

emilio commented Jan 25, 2021

If you're on mac, buttons don't get focused by mouse. That's platform behavior (WebKit does the same).

@emilio
Copy link
Collaborator

emilio commented Jan 25, 2021

It seems to me that if there's no previous active element (so, blur() was called, or there's no focused element or what not), showing the outline is the sensible thing to do. That heuristic seems to agree (or my reading of it, maybe?).

How is the user supposed to know what's focused otherwise?

@mrego
Copy link
Member Author

mrego commented Jan 25, 2021

Firefox implement the heuristics in the first comment as it passes focus-visible-{014,015,016}.html tests.

I believe I'm aligned with @emilio, I don't really care if this is just after page load, or after the user has interacted with the website. In my mind, if nothing is focused (there's no active element), and a script focus something, it's good to match :focus-visible.

So maybe the 2 heuristics in the spec could be reworded in just one, something like:

If the active element does not match :focus-visible (or there is no active element), and a script causes focus to move elsewhere, the newly focused element should match :focus-visible.

@alice
Copy link

alice commented Jan 25, 2021

I updated the codepen to use focusable divs instead of buttons - focusable divs are focused on click in WebKit and Firefox. Now I can see that Firefox shows a focus outline after programmatic focus after a blur(), but not if the blur() is skipped. (The delay makes no difference in behaviour.)

We didn't write the current language into the spec by accident; it was the result of a lot of thought and discussion, for example: WICG/focus-visible#88

A couple of questions to think about:

  • Under what circumstances do authors programmatically move focus?
  • Why would someone not currently or likely to immediately start using the keyboard want to know what the currently focused element is?

@emilio
Copy link
Collaborator

emilio commented Jan 25, 2021

We didn't write the current language into the spec by accident; it was the result of a lot of thought and discussion, for example: WICG/focus-visible#88

Sure, and I agree with the spec language :). I guess the "no active element" case really kinda falls through all the conditions of the spec, though I think the Firefox behavior is the right one, because otherwise you don't show outlines for random focus moves that the user has no way of knowing about.

Under what circumstances do authors programmatically move focus?

I expect the current spec language is pretty useful to do stuff like: Click a button, open a menu, move the focus to that menu, or stuff like that.

Why would someone not currently or likely to immediately start using the keyboard want to know what the currently focused element is?

I don't know how this question is particularly relevant to this issue, but I agree that elements that accept keyboard input should always trigger focus-visible.

@alice
Copy link

alice commented Jan 25, 2021

... I think the Firefox behavior is the right one, because otherwise you don't show outlines for random focus moves that the user has no way of knowing about.

There's not much practical difference between

newFocusTarget.focus();

and

document.activeElement.blur();
newFocusTarget.focus();

Why should they result in different behaviour?

Under what circumstances do authors programmatically move focus?

I expect the current spec language is pretty useful to do stuff like: Click a button, open a menu, move the focus to that menu, or stuff like that.

Agreed, that is what the current spec covers - those are cases where a user interaction has caused focus to move, so we cue off the user interaction.

It's exceptionally hard to think of a case other than immediately after page load when an author would move focus not in response to a user interaction.

Given that Firefox and Safari have internally inconsistent (but consistent with the operating system) behaviour for focus on click on macOS (focus is set when clicking a focusable <div>, but not when clicking a <button>, for example), it might be worth adding some language to capture the case where a user has interacted with an element which does not receive focus on click in some cases, and that interaction caused focus to move.

Why would someone not currently or likely to immediately start using the keyboard want to know what the currently focused element is?

I don't know how this question is particularly relevant to this issue, but I agree that elements that accept keyboard input should always trigger focus-visible.

It's relevant to this earlier question:

How is the user supposed to know what's focused otherwise?

My answer is that if the user is not about to use the keyboard, they don't need to know, and the remainder of the rules (including my proposed language from #5885 (comment)) ensure that in the majority of cases where they would be likely to be interested in what element is focused, the focus is shown.

@emilio
Copy link
Collaborator

emilio commented Jan 26, 2021

... I think the Firefox behavior is the right one, because otherwise you don't show outlines for random focus moves that the user has no way of knowing about.

There's not much practical difference between

newFocusTarget.focus();

and

document.activeElement.blur();
newFocusTarget.focus();

Why should they result in different behaviour?

Well, because the way you're "transferring" the knowledge of whether focus came from a pointing device or keyboard or what not in the rules in the spec is via whether the previously focused element matched :focus-visible. It's an heuristic, there's nothing saying that the page can't move the focus 10s later randomly after you click a button, and the focus won't be visible then. But the heuristic is useful because that is unlikely to happen.

My answer is that if the user is not about to use the keyboard, they don't need to know, and the remainder of the rules (including my proposed language from #5885 (comment)) ensure that in the majority of cases where they would be likely to be interested in what element is focused, the focus is shown.

Well, sure, but you can't guess intent from a focus() call. For example, I find the firefox behavior quite useful when I'm going back to a tab (Firefox will programmatically restore the focus to where it was, so I know where the next tab key press will get me).

@fvsch
Copy link

fvsch commented Jan 29, 2021

As a UI developer, I like the current heuristic on paper. It’s simple enough that it can be explained, which helps to a) use it right and b) work around it in edge cases.

But if I understand it right, the specifics of “no focus for clicked buttons” on macOS (WebKit and Firefox) make things much less straightforward when clicking a button then moving the focus programmatically.

Is it correct that it makes it impossible, on macOS, to programmatically move focus to a target element — e.g. the first focusable element in a modal — after a click on a button and have :focus-visible NOT apply to that target element?

This could mean that we will have to avoid using :focus-visible styles as product owners and QA report bugs with steps-to-reproduce such as:

  1. Click the button to open a modal.
  2. The modal shows up on the screen. In the modal, the first button [or link] has a big blue border.

Remove that border.

The only workarounds I can think of all damage accessibility, e.g.:

  • do not move focus programmatically (okay in some cases, but it can be required for some tabs or modal patterns);
  • or, when moving focus to a target container, remove the focus outline on that target (for all users, including keyboard users);
  • or make the focus outline visually subtler (probably making it hard to notice, or using contrast below 3:1).

@bkardell
Copy link
Contributor

So, it seems there is some confusion here and I think that some of this actually comes from how we have written things as much as anything else.

Here are the current heuristic rules from the spec. They are bullets in the spec, but I am using numbers to make it a little easier to compare, but I guess I also think they are ordered points...

  1. If a user has expressed a preference (such as via a system preference or a browser setting) to always see a visible focus indicator, the user agent should honor this by having :focus-visible always match on the active element, regardless of any other factors. (Another option may be for the user agent to show its own focus indicator regardless of author styles.)

  2. Any element which supports keyboard input (such as an input element, or any other element which may trigger a virtual keyboard to be shown on focus if a physical keyboard is not present) should always match :focus-visible when focused.

  3. If the user interacts with the page via the keyboard, the currently focused element should match :focus-visible (i.e. keyboard usage may change whether this pseudo-class matches even if it doesn’t affect :focus).

  4. If the user interacts with the page via a pointing device, such that the focus is moved to a new element which does not support user input, the newly focused element should not match :focus-visible.

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

  6. 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.


I kind of think that we're getting trapped in some words/phrasing... I think this is the problematic part "If the active element matches :focus-visible, and a script causes focus to move elsewhere". I believe that what I am seeing is mostly that how it is being read isn't uniform: Emilio has interpreted this (I think) as "as focus is initiated, look to see if there is an active element". Thus, if a blur has happened, there isn't, so to him his treatment makes sense. However, maybe a better way to say this is ""we look at how they last interacted with the page". Thus, if you look at rego's examples, and consider Alice's followons about blur and her pens, you can see that she is trying to show that's not right. I kind of personally feel like our initial take on this which talked somehow about modality was important. I kind of still wonder if there should be some concept like that, as least in words (though, in practice even a prop might be good)...

In any case, I have attempted to provide a modified set of rules that @alice and I, I think, would agree too and I wonder if make the intents clearer?

  1. If a user has expressed a preference (such as via a system preference or a browser setting) to always see a visible focus indicator, the user agent should honor this by having :focus-visible always match on the active element, regardless of any other factors. (Another option may be for the user agent to show its own focus indicator regardless of author styles.)

  2. Any element which supports keyboard input (such as an input element, or any other element which may trigger a virtual keyboard to be shown on focus if a physical keyboard is not present) should always match :focus-visible when focused.

  3. If the user interacts with the page via the keyboard, the currently focused element should match :focus-visible (i.e. keyboard usage may change whether this pseudo-class matches even if it doesn’t affect :focus).

  4. If the user interacts with the page via a pointing device, such that the focus is moved to a new element which does not support user input, the newly focused element should not match :focus-visible.

  5. If the user has not interacted with the page, and a script (or similar behavior via autofocus) causes focus to be set, the newly focused element should match :focus-visible

  6. If the user's last interaction with the page would cause an element to match :focus-visible, and a script causes focus to move elsewhere, the newly focused element should match :focus-visible.

  7. Conversely, if the user's last interaction with the page would cause an element to to not match :focus-sible, and a script causes focus to move elsewhere, the newly focused element should not match :focus-visible.

@astearns astearns added this to the VF2F-2021-02-09 EUR milestone Feb 2, 2021
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [selectors] :focus-visible matches on initial programmatic focus.

The full IRC log of that discussion <dael> Topic: [selectors] :focus-visible matches on initial programmatic focus
<dael> github: https://github.com//issues/5885
<Rossen_> q
<dael> rego: This is about if focus-visible should match after programmatic focus. Current heuristics, though not normative, talk about if active element matches then next element will match. Spec didn't mention anything about if there's no active element
<dael> rego: Proposal is to change a bit so instead of saying it's the active element, you have to check last user interaction. ANd if none you match focus-visible. But if someone clicked a button and there was blur you wouldn't match b/c first was not a mouse
<dael> emilio: What interactions count and which don't? That's an issue. I think current heuristic is fine. It's a problem on mac b/c mac doesn't focus a bunch when you click on it. A lot of programs when you click and then move focus the heuristics say button wasn't focused so new thing shouldn't mtach
<dael> emilio: On mac since element isn't focusable by mouse you hit this issue. Good thing to come up with something that works. Gneerally agree with proposal, but which interactions count and which don't? I want the definition to be clear
<dael> emilio: Does an interaction a second ago prevent programmatic focus from matching?
<dael> florian: Confused. Are we trying to define when a UA shows a focus ring? If not, why defining when focus-visible shows?
<dael> rego: Everyone is following heuristics. If we have clear heuristics then it's better
<dael> florian: They're supposed to be heuritstics about when they show focus ring and focus-visible matches.
<dael> emilio: True
<dael> florian: So we're talking about the combo, not getting out of sync
<dael> emilio: Right
<dael> emilio: I'm okay with the proposal. I jsut want whatever this interaction means to be clarified
<dael> florian: Wondering if right spec and place. I htink CSS spec has what it needs. We match browser focus ring and the pseudo class. When the browser shows focus feels more like HTML. Should we move them and call them normative?
<dael> emilio: It's an option. Fine with that
<dael> florian: If discussing state of doc and state of UI it's not a very css-y topic
<dael> fantasai: This text is just an example. It's not normative
<dael> florian: If you want normative it belongs in HTML, right?
<dael> Rossen_: What are we doing with this?
<dael> rego: Keep going on the issue and see if we should move this
<dael> florian: Just before we wrap up, my impression is even though called heuristisc we're trying to harmoize browsers about when they show/don't show focus ring
<dael> Rossen_: And make it more detectable
<dael> florian: We have detectable. We have the pseudo class that matches browser behavior. If we want to define browser we should have that in html
<dael> Rossen_: I think right next step are add any pseudo class discussions in the topic and rego will continue working with html group to see if there's additional behavior to define there

@mrego
Copy link
Member Author

mrego commented Mar 1, 2021

From the last time we discussed this:

emilio: What interactions count and which don't?

I think it's important we define this, does a random click on any part of the page is an interaction related to this or not? And there are other kind of special situations in which it'd be nice to define what's a meaningful interaction regarding these heursitics.

For that reason I created a series of tests with different examples and use cases (maybe more could be added), it'd be nice to reach an agreement in how they should work before we can prepare the spec text (even the the spec text would be for HTML spec and not the CSS one): web-platform-tests/wpt#27806

moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Mar 18, 2021
…lt. r=mac-reviewers,bradwerth,mstange

This aligns Mac's focus model with other platforms. Matches Chromium, but not
Safari.

Reasons why I think it's worth making this change:

 * Consistency with all other platforms.
 * Makes the :focus-visible implementation more useful.
 * Fixes focus navigation after e.g. clicking a button.
 * Shouldn't cause a lot more outlines to show up (at least not by default).

An example of the second point:

    data:text/html,<button onclick="this.nextElementSibling.focus()">Click</button><button>Imagine I'm a dialog close button or something</button>

In non-macOS platforms, we won't show an outline for the button in that case,
which matches the developer expectations (links below). We don't show the
outline because the focus comes from an element that has been focused by mouse
(and thus didn't show an outline). But on macOS that doesn't work, because the
button is not focused.

For completeness, the actual heuristics for :focus-visible may change a bit as
a result of the discussions in:

  * w3c/csswg-drafts#5885
  * web-platform-tests/wpt#27806

But it's not clear to me how to best define this so it works on the macOS focus
model.

An example of the third point:

    data:text/html,<input type=text><input type=submit><input type=text>

On Safari and Chrome (and Firefox on non-macOS platforms), clicking the button,
then pressing tab, goes to the input on the right. In Firefox on macOS it
doesn't because the button doesn't gain focus nor is selectable.

Differential Revision: https://phabricator.services.mozilla.com/D108808
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Mar 18, 2021
What we implemented before this patch was basically what the heuristics
in the spec said, which used to be normative:

  https://drafts.csswg.org/selectors/#the-focus-visible-pseudo

That has become non-normative and there's ongoing discussion on what
should happen for cases like this in:

  w3c/csswg-drafts#5885
  web-platform-tests/wpt#27806

There seems to be agreement on that WPT issue on cases like this one, so
let's make it work.

Differential Revision: https://phabricator.services.mozilla.com/D108805
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Mar 18, 2021
What we implemented before this patch was basically what the heuristics
in the spec said, which used to be normative:

  https://drafts.csswg.org/selectors/#the-focus-visible-pseudo

That has become non-normative and there's ongoing discussion on what
should happen for cases like this in:

  w3c/csswg-drafts#5885
  web-platform-tests/wpt#27806

There seems to be agreement on that WPT issue on cases like this one, so
let's make it work.

Differential Revision: https://phabricator.services.mozilla.com/D108805
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Mar 19, 2021
What we implemented before this patch was basically what the heuristics
in the spec said, which used to be normative:

  https://drafts.csswg.org/selectors/#the-focus-visible-pseudo

That has become non-normative and there's ongoing discussion on what
should happen for cases like this in:

  w3c/csswg-drafts#5885
  web-platform-tests/wpt#27806

There seems to be agreement on that WPT issue on cases like this one, so
let's make it work.

Differential Revision: https://phabricator.services.mozilla.com/D108805
@SaidMarar
Copy link

SaidMarar commented Jan 30, 2023

Hi all,

Well i agree with @mrego "the user interacts with the page" is not clear.
Is it interacting with focusable elements ? or with everything in the page ?

In this example the rule number 4 fails when we mouse-click on an element that is not focusable and we move focus to another element with an initial programmatic focus.

Current behavior:

As long as we dont click on focusable element the :focus-visible will always match after initial programmatic focus.

Please check the example for detailed scenarios.

@oliviertassinari
Copy link

oliviertassinari commented Jul 13, 2024

Is there any discussion to drop this ":focus-visible matches on initial programmatic focus" heuristic? I'm trying to understand why it behaves like this by default. It breaks my intuition.

I have experienced two wrong end-user behaviors that feels like comes from this heuristic:

  1. [Select] - First options receives .Mui-focusVisible always mui/material-ui#23747
  2. https://next.mui.com/ :focus-visible triggers but feels like it makes no sense in this context:
Screen.Recording.2024-07-14.at.01.38.14.mov

@emilio
Copy link
Collaborator

emilio commented Jul 14, 2024

I think the heuristic generally makes sense. If there's a programmatic call where it doesn't you can pass focusVisible: false to focus()

@oliviertassinari
Copy link

oliviertassinari commented Jul 14, 2024

@emilio to rely on element.focus({ focusVisible: true/false }) https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#focusvisible / https://html.spec.whatwg.org/multipage/interaction.html#focus-management-apis. I imagine that we need to:

  • change the spec to support false too, it seems to be only about forcing true in the option description.
  • implement our own focus-visible polyfill, equivalent to https://github.com/WICG/focus-visible. We would need to set the value conditionally. Or actually, likely simpler, only using the origin event type.

Maybe it would make sense to have element.focus({ modality: 'keyboard' / 'pointer' }) instead. I would tell the browser the origin modality of the focus, did it come form a mouse over, a keydown event, etc?

@emilio
Copy link
Collaborator

emilio commented Jul 14, 2024

change the spec to support false too, it seems to be only about forcing true in the option description.

Wdym? False is supported, MDN is just wrong. The spec differentiates between true, false, and not provided (which is where the heuristic kicks in)

@oliviertassinari
Copy link

oliviertassinari commented Jul 14, 2024

@emilio I see only a mention of the behavior when focusVisible: true

SCR-20240714-pgnp

https://html.spec.whatwg.org/multipage/interaction.html#dom-focusoptions-focusvisible

@emilio
Copy link
Collaborator

emilio commented Jul 14, 2024

Right, thus if it's false it never indicates focus, which means it'll not match the pseudo-class.

The spec seems clear, but I wrote it so I'm obviously biased :)

But the spec is basically saying (in JS terms):

let matchesFocusVisible = options.focusVisible || (options.focusVisible === undefined && heuristicMatches);

Right? Which means that the false case is well-defined, it just works by not indicating focus.

I'll send an MDN PR tomorrow once I'm on my desktop, if I don't forget :)

@oliviertassinari
Copy link

oliviertassinari commented Jul 16, 2024

Right, thus if it's false it never indicates focus, which means it'll not match the pseudo-class.

@emilio it feels like this contradict this issue description:

We have a test focus-visible-010.html that checks that a programmatic focus on the load event, causes that the element getting focused matches :focus-visible. This test passes in the 2 implementations of :focus-visible (Chromium and Firefox).

@emilio
Copy link
Collaborator

emilio commented Jul 16, 2024

That test is not using focusVisible: false, thus using the heuristic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
selectors-4 Current Work
Projects
None yet
Development

No branches or pull requests

9 participants