WICG / focus-visible

Polyfill for `:focus-visible`
https://wicg.github.io/focus-visible/demo/
Other
1.6k stars 124 forks source link

Handling `:focus-within` equivalent. #151

Open plinehan opened 6 years ago

plinehan commented 6 years ago

Is there any plan to handle the equivalent of :focus-within for the :focus-visible pseudo-selector in the CSS spec? Something like :focus-visible-within?

I modified [my version of] the script to add the focus-visible class to the body whenever focus is visible. Then I can write a selector like body.focus-visible foo:focus-within to style foo when focus is visible within it, but I don't see an obvious way to do that in pure CSS once the spec is updated.

robdodson commented 6 years ago

That is a great question, and yeah, I don't know how you would do this without something like :focus-visible-within. I'll add this as a note for @alice and I to discuss when she gets back from vacation. Thanks for filing it!

alice commented 6 years ago

Agreed, this is a great question.

What is the use case which motivated you to make this change in your fork of the script?

plinehan commented 6 years ago

They are almost all cases where we have an input and a set of associated buttons (e.g. a search input box and a "search" button) and we'd like the focus to be around the outer container. I think in most of these we could just use focus-within since the input is always focus visible, but I can imagine cases it would be nice to detect if the search button was visibly focused or not.

We have a few other cases where it just works out conveniently to use focus-within due to how to DOM happens to be constructed. We're retrofitting correct indicators into an extant codebase, so it is handy to have this kind of power :)

alice commented 6 years ago

Good use case, thanks!

This is annoyingly making me think about modality again, like the original idea @bkardell had... it seems wacky to have :focus, :focus-within, :focus-visible and :focus-visible-within.

Will keep thinking about it.

OliverJAsh commented 5 years ago

We also just ran into this. I have a hidden input inside a label, which I apply focus styles using :focus-within. When the label (and thereby input) is actioned via mouse users, we end up showing focus styles. (Unfortunately the team decided to blur the input immediately after use to avoid this.)

Justineo commented 5 years ago

I think :has(:focus)/:has(:focus-visible) are better than :focus-within/:focus-visible-within. Maybe :has is too big a thing to proceed?

MarcoZehe commented 4 years ago

@t3chguy and I are currently beating around something like this in the Riot web client. A toolbar only shows if the mouse hovers over the relevant message. We are at a point where the action toolbar shows up if screen readers or keyboard users move into the actual message tile, but the way CSS is set up right now, with the .focus-visible polyfill, the toolbar disappears once the screen reader moves focus to a button inside the toolbar. Even though that is within the toolbar and on an actionable button. So having this would be really awesome, since we can't seem to convince the powers that be to always show the toolbar for each message in the chat.

robdodson commented 4 years ago

@MarcoZehe is there a demo we could play with? I'm having a hard time understanding the use case.

robdodson commented 4 years ago

@OliverJAsh sorry for the lag. Can you show me what your markup looked like?

MarcoZehe commented 4 years ago

The use case is Riot Chat that shows an action bar for each message when hovering over it. However, there are a few situations where this doesn't work reliably, for example for screen readers, we adjusted this to also be :focus-within, but that doesn't fire in all situations. We need to do this so screen reader and keyboard users (once full keyboard access is implemented) have access to all controls no matter where within each message container the focus happens to be.

OliverJAsh commented 4 years ago

@robdodson https://jsbin.com/fokexoc/1/edit?html,css,output

robdodson commented 4 years ago

hm yeah both of these make sense. It looks like the TAG folks added a CSS selectors level 5 label to the prior discussion here: https://github.com/w3c/csswg-drafts/issues/3080#issuecomment-437048758

I wonder if they're thinking of exploring the :focus() or :has() option...

robdodson commented 4 years ago

@MarcoZehe That makes sense. You're basically replacing hover with focus-visible for folks using assistive tech, but that means that if the hover card contains something focusable you want to keep it from closing when the user tabs to that focusable child inside of the card.

Since you mentioned that the user is moving focus inside of the toolbar, could you use focus-within on the toolbar itself to keep it from disappearing?

robdodson commented 4 years ago

@OliverJAsh yeah that's a tricky example... It feels like the issue is really with <input type="file"> and the fact that you have to use the label hack to make it styleable. In other words, if you could just style the <input type="file"> button you wouldn't need :focus-visible-within, correct?

t3chguy commented 4 years ago

Since you mentioned that the user is moving focus inside of the toolbar, could you use focus-within on the toolbar itself to keep it from disappearing?

Not quite because then clicking on a toolbar item would mean the toolbar stays open (even after hover is removed), which is undesirable in this case.

OliverJAsh commented 4 years ago

In other words, if you could just style the <input type="file"> button you wouldn't need :focus-visible-within, correct?

@robdodson Correct! Although consider this example where we don't want to hide the input: we want to give the label :focus-visible styles when the input within has focus https://jsbin.com/wexehirubu/1/edit?html,css,output

robdodson commented 4 years ago

@t3chguy

Not quite because then clicking on a toolbar item would mean the toolbar stays open (even after hover is removed), which is undesirable in this case.

Is that because toolbar items are custom components using tabindex? I think a <button>, for instance, won't retain focus in Safari or Firefox, but will in Chrome. I'm mostly asking because if <button> didn't retain focus, I'm curious if it would fix your issue.

t3chguy commented 4 years ago

they are custom components using tabindex but I don't think the proposed fix would solve my issue without testing

robdodson commented 4 years ago

ah yeah. I believe all browsers retain focus when you click on something with tabindex.

Do you want the toolbar to close when someone using a keyboard clicks on the item? Because I think it would still be matching :focus-visible-within at that point. If the button already has focus and you hit spacebar, I don't think it blurs focus.

So I think the only way to work around that would be to programmatically move focus out of the toolbar. Which makes me wonder if :focus-visible-within would actually be useful in this scenario?

miragecraft commented 3 years ago

I have a scenario where :focus-visible-within would come in very handy: https://codepen.io/Miragecraft/pen/mdrRLXw

bkardell commented 3 years ago

Yeah, we've discussed this and a lot of other things like it in csswg. We know we should do something here, it's just not clear what. :has could, in theory solve a lot of things but in practice it is unclear it is implentable. Alternatively, a hundred withins would create their own problems. Sorry I know that's not a real helpful reply. Just wanted to say those are the challenges.

miragecraft commented 3 years ago

By "a hundred withins" I assume you are referring to other pseudo-classes. I really don't feel just by adding :focus-visible-within it suddenly means every other pseudo-class needs a -within variant too, and for consistency sake it makes more sense to have a -within variant to :focus-visible to align with :focus and :focus-within than not having one.

In addition, I feel that :focus and :focus-visible benefits the most from having the -within variant due to them being critical for accessibility, so adding this one variant gives the greatest return on investment.

unilynx commented 3 years ago

+1 for focus-visible-within. If I can give my use-case:

image

This is a <details><summary> ... and the blue focus border is around the summary. giving how it cuts through the element, we'll probably switch to focus-visible for this. but ideally i would highlight the entire 'details' element (focus-within) but only when needed (thus... focus-within-visible)

bathos commented 3 years ago

I agree with @miragecraft — this slope doesn’t seem very slippery? I understand that it seems to cry out for generalization, but the need for :focus-visible-within is pressing in a more we-feel-it-every-day way than other possible functionality that generalized “within” selectors might unlock — and correct me if I’m wrong, but I’d guess that it doesn’t present the same performance challenges as a generalized within selector?

This may sound silly, but :focus-visible has been life-changing. We no longer have to dread the we’ll-just-ask-the-new-intern-to-disable-these-focus-rings-if-you’re-gonna-be-so-annoying-about-it workaround. But the matrix is incomplete — without :focus-visible-within, there’s still a subset of cases (which seem pretty random to non-developers) where we have to say “no” to the inaccessibility overlords. In the end, that “no” doesn’t always work even if you stand your ground.

To put it another way, the absence of :focus-visible-within, like :focus-visible before it, has consequences on the net accessibility of the web. The reasons for that are embarrassing, but it’s still what often happens. This seems more important than whether the selector is maximally DRY.

robdodson commented 3 years ago

I think there's still some discussion around adding it to :has() in the csswg, https://github.com/w3c/csswg-drafts/issues/3080#issuecomment-839559449

In my opinion this may be the best option?

Justineo commented 3 years ago

A good news: folks from Igalia is working on implementing :has for Chromium.

https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c17

lgenzelis commented 2 years ago

Does anyone know if any progress have been made here?

bathos commented 2 years ago

yes!! :has is real, sorta! nobody's shipped it on a main channel yet, but [Safari TP has it](https://developer.apple.com/safari/technology-preview/release-notes/#:~:text=collapse%20properties%20(r287429)-,%3Ahas(),-Added%20style%20invalidation) and Chromium work seems to be ongoing/active (there are related commits from this week it looks like). I'm not how to interpret FF's fourteen(!)-year-old issue though. There are associated issues that look active, but it isn't clear to me whether they're directly related or just "other stuff in selectors 4".

danbrellis commented 2 years ago

An additional use case is having a checkbox wrapped by the label where you are styling the label and 'hiding' the checkbox:

<label><input type="checkbox" />Label</label>
label {
  background: blue;
  color: #fff;
  display: inline-block;
}
input {
  appearance: none;
}

When tabbing, the focus is set to the checkbox, so if you want to style the label, you need to use focus-within.

label:focus-visible, label:focus-within {
  outline: 2px solid #21578A;
}
label:focus:not(:focus-visible){
  outline: none;
}

The above css still shows the outline around the label when tabbing.

EDIT sorry, just realized this is the same use case as brought up by @OliverJAsh

craigkovatch commented 2 years ago

We have a use case for this as well, which I think is a fairly common hack, to show custom focus indicators on checkbox/radio elements using ::before (or ::after, whatever) pseudos. My current focus rule looks like &.focus-within.focus-visible::before. These can't be translated directly to the newly-available (yay!) :focus-visible pseudo because it doesn't match in the way that I don't need to explain since that's the whole reason for this Issue :D

Long-winded way of saying +1, but another I-think-common way of needing it.

Dariaky commented 2 years ago

Another use case for :focus-visible-within

<div class="logo" >
    <a href="/brand">
        <img src="assets/icons" alt="logo image">
    </a>
</div>
.logo {
    width: 50px;
    height: 50px;
    display: block;
    overflow: hidden;
}

.logo:focus-within {
    outline: 2px solid blue;
}

When parent container has overflow: hidden and no possibility to change that, :focus-within helps to make indicator visible, otherwise outline of the <a> is trimmed/overlapped. Focus should be visible only for keyboard users.

matthew-dean commented 2 years ago

I ran into this today! Had a label wrapped around a radio button / span. The radio button is what I want to detect :focus-visible on, and apply :focus-within on the surrounding label. I hacked around it by absolutely positioning a wrapper from the span to the surrounding box.

bathos commented 2 years ago

Update re: :has for @lgenzelis & anyone else:

caniuse :has entry

matthew-dean commented 2 years ago

@bathos I wonder if when :has(:focus) is supported, if :focus-within will be deprecated. Seems clumsy and limited at that point.

bathos commented 2 years ago

@bathos I wonder if when :has(:focus) is supported, if :focus-within will be deprecated. Seems clumsy and limited at that point.

I wouldn’t think so. To the best of my knowledge those aren’t equivalent. The :focus selector matches elements that are focused. The :has(:focus) selector matches elements with descendent elements that are focused. The :focus-within selector matches elements whose flat tree descendents (i.e. as if shadow root children were the actual children of their hosts and slots were replaced by their assignees) include nodes (which could be text nodes) that have focus. That’s something pretty different from :has(:focus). In fact :has(:focus-within) is itself something new and useful.

craigkovatch commented 2 years ago

Text nodes are inert, they can’t be focused, even in shadow dom…right?

bathos commented 2 years ago

@craigkovatch I’m ... not really sure.

CSS doesn’t define the conditions for what a UA considers to be a focused node, only a few constraints on how it would need to be defined. The spec explicitly says the set of flat-tree descendents whose status as focused would make their flat-tree ancestor match :focus-within “[includes] non-element nodes, such as text nodes.”

This could be an artifact of CSS’s generality — it is not specifically an HTML styling language, so it’s agnostic to anything HTML might tell us about what can and cannot be focused here. However even HTML leaves this pretty open-ended. HTML’s notion of focusable areas includes things which aren’t elements, e.g. “scrollable areas”, and again UAs are left free to decide what the exact set of focusable areas are provided certain criteria are met.

Focusable areas can be elements, parts of elements, or other regions managed by the user agent.

I don’t know if there are UAs that consider text nodes to be focused in this sense in practice, but if I understand right they are permitted to. I’d be very curious to learn more about this too — sorry I can’t answer this more properly.

(It does seem like it’d be fair to say :focus-within exposes something closer to the actual breadth of potential UA focus to HTML and CSS, though, as it satisfies the requirement that UA focus status be exposed via some element but eliminates the requirements that the focused-thing also be (a) that element (b) an element (c) exposable and (d) reified anywhere at all.)

lgenzelis commented 2 years ago

@bathos

The :has(:focus) selector matches elements with descendent elements that are focused

I would have sweared that's what :focus-within did. 😅 I'm sorry, I still don't quite understand the distinction. I'm not familiarized with the technical lingo, so I don't get the references to e.g. the flat tree.

Could you please mention an example where :has(:focus) would act differently than :focus-within? Is at least one of the two a subset of the other?

bathos commented 2 years ago

@lgenzelis In a tree of DOM nodes, some elements may “host” ShadowRoots. Each ShadowRoot is a little bit like its own document (trying to keep this concise — this explanation isn’t gonna capture any nuance) that can provide forms of encapsulation (including encapsulation of CSS) and functionality typically leveraged by custom elements. If you are not using attachShadow anywhere, then the only time you encounter ShadowRoot nodes would be those of the user agent’s shadows (some of which expose interior components to CSS via pseudo-element selectors), but AFAIK :focus-within does not come into play with those.

Briefer: :focus-within will continue to be important for folks using shadow DOM, but if you don’t use shadow DOM, you may never have occasion to observe a distinction from :has(:focus).

lgenzelis commented 2 years ago

Crystal clear, thanks a lot @bathos ! ^_^

craigkovatch commented 2 years ago

@bathos if you grep that spec, the "non-element nodes, such as text nodes" is repeated in four different places, so my guess is it's just a generalized statement about certain pseudos, rather than something specifically added about focus behavior.

Text nodes can't be focused. Focus is about interactivity, and text nodes aren't interactive. Put another way -- if a text node were interactive, it's no longer a text node.

afraser commented 2 years ago

This would be lovely for handling focus state on the label for a radio input. Sounds like I'm not the only one who thinks so.

Visual example: CleanShot 2022-06-08 at 09 23 25

dcleao commented 1 year ago

@bathos

  • No signal from Firefox as far as I’m aware (though I may not know the right places to look)

From https://bugzilla.mozilla.org/show_bug.cgi?id=418039#c62, at the 8th of January of 2023:

We plan to work on this in the first half of this year.

yisibl commented 5 months ago

The :has() selector is supported by all major browsers, so it's time to discuss the solution in Shadow DOM.