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

Handling :focus-within equivalent. #151

Open
plinehan opened this issue Jun 19, 2018 · 44 comments
Open

Handling :focus-within equivalent. #151

plinehan opened this issue Jun 19, 2018 · 44 comments
Labels

Comments

@plinehan
Copy link
Contributor

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
Copy link
Collaborator

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
Copy link
Member

alice commented Jun 27, 2018

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
Copy link
Contributor Author

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
Copy link
Member

alice commented Jun 27, 2018

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
Copy link

OliverJAsh commented Nov 8, 2018

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
Copy link
Contributor

Justineo commented Nov 8, 2018

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
Copy link

@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
Copy link
Collaborator

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

@robdodson
Copy link
Collaborator

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

@MarcoZehe
Copy link

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
Copy link

@robdodson
Copy link
Collaborator

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: w3c/csswg-drafts#3080 (comment)

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

@robdodson
Copy link
Collaborator

@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
Copy link
Collaborator

@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
Copy link

t3chguy commented Jan 14, 2020

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
Copy link

OliverJAsh commented Jan 14, 2020

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
Copy link
Collaborator

@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
Copy link

t3chguy commented Jan 30, 2020

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

@robdodson
Copy link
Collaborator

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
Copy link

miragecraft commented Dec 14, 2020

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

@bkardell
Copy link
Collaborator

bkardell commented Dec 14, 2020

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
Copy link

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
Copy link

unilynx commented Feb 26, 2021

+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
Copy link

bathos commented May 12, 2021

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
Copy link
Collaborator

I think there's still some discussion around adding it to :has() in the csswg, w3c/csswg-drafts#3080 (comment)

In my opinion this may be the best option?

@Justineo
Copy link
Contributor

Justineo commented Jun 3, 2021

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
Copy link

Does anyone know if any progress have been made here?

@bathos
Copy link

bathos commented Feb 8, 2022

yes!! :has is real, sorta! nobody's shipped it on a main channel yet, but Safari TP has it 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
Copy link

danbrellis commented Mar 18, 2022

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
Copy link

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
Copy link

Dariaky commented Mar 31, 2022

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
Copy link

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
Copy link

bathos commented May 22, 2022

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

  • Safari supports :has in stable as of 15.4 on March 14, 2022 (both desktop and iOS).
  • Chromium intent-to-ship announced May 16, 2022 w/ estimated milestone 105 (if estimate ends up accurate, that means it’d be released on the stable channel on August 30, 2022 per chromium release schedule).
  • No signal from Firefox as far as I’m aware (though I may not know the right places to look)

caniuse :has entry

@matthew-dean
Copy link

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

@bathos
Copy link

bathos commented May 23, 2022

@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
Copy link

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

@bathos
Copy link

bathos commented May 23, 2022

@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
Copy link

@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
Copy link

bathos commented May 23, 2022

@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
Copy link

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

@craigkovatch
Copy link

@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
Copy link

afraser commented Jun 8, 2022

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
Copy link

dcleao commented Jan 26, 2023

@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
Copy link

yisibl commented May 23, 2024

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

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

No branches or pull requests