Open mrego opened 3 years ago
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.
I think we should consider 2 cases:
autofocus
.
<div id="target" tabindex="0">Target</div>
<script>
window.addEventListener("load", () => target.focus());
</script>
<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.
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?
Firefox implements that heuristic. But there's no active element since blur() was called, so that heuristic doesn't apply, what am I missing?
If you're on mac, buttons don't get focused by mouse. That's platform behavior (WebKit does the same).
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?
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.
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: https://github.com/WICG/focus-visible/issues/88
A couple of questions to think about:
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.
... 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 https://github.com/w3c/csswg-drafts/issues/5885#issuecomment-765008329) ensure that in the majority of cases where they would be likely to be interested in what element is focused, the focus is shown.
... 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).
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:
- Click the button to open a modal.
- 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.:
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...
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.)
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.
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
).
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
.
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.
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?
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.)
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.
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
).
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.
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
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
.
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
.
The CSS Working Group just discussed [selectors] :focus-visible matches on initial programmatic focus
.
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): https://github.com/web-platform-tests/wpt/pull/27806
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.
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.
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:
:focus-visible
triggers but feels like it makes no sense in this context:https://github.com/user-attachments/assets/7f531d12-ee7b-44f0-a75e-848f857cc166
I think the heuristic generally makes sense. If there's a programmatic call where it doesn't you can pass focusVisible: false
to focus()
@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:
false
too, it seems to be only about forcing true
in the option description.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?
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)
@emilio I see only a mention of the behavior when focusVisible: true
https://html.spec.whatwg.org/multipage/interaction.html#dom-focusoptions-focusvisible
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 :)
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).
That test is not using focusVisible: false
, thus using the heuristic.
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:
But it doesn't mention what happens when there's no active element before the programmatic focus. WDYT?
CC @alice @emilio