:focus-visible-within, the missing pseudo-class

As developers and designers, we know that the user experience is key to the success of any application. That's why it's important to pay attention to the details, including the behaviour of elements when they are in focus. The CSS pseudo-classes for styling focus have different behaviours and use cases, and there's even a use case we don't have a class for!

Lars smiling

Published β€” 9 min read

The CSS pseudo-classes :focus, :focus-visible, and :focus-within all target elements that are currently in focus, but the difference between them can be confusing.

Let's take a closer look at these focus pseudo-classes and explore the differences between them, and find out how you can style elements the way you want in all scenarios.

By understanding how each of these pseudo-classes work, you'll be better equipped to create accessible and user friendly experiences for everyone.

What are the focus pseudo-classes?

There are three CSS pseudo-classes that target elements that are currently in focus, but there are some differences in their behaviour:

  1. :focus: Applies to an element that currently has focus. Typically used to style form elements.
  2. :focus-within: Applies to the parent element when any of its descendants have focus. Useful for styling parent elements based on child focus.
  3. :focus-visible: Applies to the element in focus 'when it's visible to the user'. Useful for providing visual cues to keyboard users.

Button focus states won't work as expected in Safari.

Other browsers give focus to clicked buttons, butΒ Safari does not, by design.

Digging into :focus-visible

Out of these three, :focus-visible may be the most confusing one. What does it even mean that it applies to the element in focus 'when it's visible to the user'?

When you declare your own CSS using the various CSS pseudo-classes you're actually changing when you want a focus indicator to apply to the button. Not just how the styling will look.

Let's start off by styling a button to see what happens.

.my-button:focus {
  outline: 2px solid hotpink;
}

Try pressing the button, and using the keyboard to navigate to it.

This example doesn't work in Safari. You should see a pink outline both when you click on or tab to the button.

Here you've actually explicitly told the browser: "I want focus to always appear and I want it to have these styles", so this is what the browser does:

  • Clicking the button with a mouse: the browser adds :focus to the button
  • Navigating to the button with your keyboard: the browser adds :focus to the button

Now let's delete these focus styles and look at a <button> with no focus styles, only the browser defaults:

Again, try pressing the button, and using the keyboard to navigate to it.

You should see the browser default outline only when navigating to the button with your keyboard.

  • Clicking the button with a mouse: the browser does nothing 🀯
  • Navigating to the button with your keyboard: the browser adds :focus-visible to the button

Why doesn't the browser indicate some sort of focus when you're clicking the button?

Now we're at the crux of why we're showing that an element has focus in the first place, as well as why :focus-visible needs to exist.

  • All focus indicators exist to show people where they are and what they're doing.
  • :focus-visible is a focus indicator that only shows up when it's helpful for people.

Another way to look at :focus-visible and its helpfulness is to think about the difference between using a mouse (or your finger) and using a keyboard.

If you're using a mouse and clicking a button it's likely not going to be helpful to show you a focus indicator on the button. You found the button, so you know where you are. You clicked the button, so you know what you're doing.

In this case, showing a focus indicator may actually be distracting and confusing.

However, if you're using a keyboard to navigate to the button it's critical that a focus indicator shows you where you are, and that you can press the button so you know what you're doing.

How you can use :focus-visible and the other pseudo-classes

Instead of using :focus when you write your own CSS, opt for styling elements using the :focus-visible pseudo-class first, and only use the other classes if that doesn't seem right. Let the browser do the heavy lifting to figure out when focus should be applied.

  1. :focus: Use this to force a focus indicator on an element when it's important to show focus.
  2. :focus-within: Use this if you're building a custom component with interactive elements and it would only make sense to show focus on a parent element, or if it's helpful to clearly see that your element contains a child element that is focused.
  3. :focus-visible: Use this by default to only show focus on an element when it's helpful to the user-experience. Always test this with your keyboard as well.

Getting a better overview of the focus pseudo-classes

Let's map the focus CSS pseudo-classes to a matrix to get a full overview of what we're working with.

  • y-axis: describes when the browser will apply the pseudo-classes if they're defined in your CSS.
  • x-axis: describes which element the browser will check for focus to determine if your CSS should be applied to your selector.
AppliesTargets selfTargets ancestors
Always applies:focus:focus-within
Only when helpful:focus-visible?

Hey, we're missing something in the bottom right quadrant!

What if we wanted to target ancestor elements only when showing focus would be helpful?

Or in other words, how can we show focus on a parent element if and only if one of its children has :focus-visible?

Introducing ":focus-visible-within", a pseudo-class that doesn't exist πŸ₯

:focus-visible-withinΒ is not a real CSS pseudo-class, it's only a mental model I like to use when describing this missing piece of the focus indicator puzzle: Wanting to style an element based on whether one of its children has focus, but only showing an indicator when it's helpful. πŸ€”

Luckily, with the recent introduction of the :has() CSS pseudo-class, we're able to do complex selector matching against a target element.

If you're not yet familiar, think of :has() as a way to style the element that comes before the ":" by checking if it matches or contains what you pass into the function "()". Like section:has(h2) which selectively styles all <section>s, but only when they contain one or more <h2>s.

:has() is pretty much a 'parent' selector with some extra superpowers.

Using :has() to emulate ":focus-visible-within"

It's really quite simple by writing :has(:focus-visible).

Here's what ":focus-visible-within" using :has() would look like in a more fleshed out example:

.input-group:has(:focus-visible) {
  outline: 4px solid hotpink;
}
This is a group with a button.
  • You should see a pink outline around the .input-group when you navigate to the <button> with your keyboard, but not when you click it with your mouse.
  • The <button> should show the browser default outline when navigated to with your keyboard.

Now the .input-group will only get a visible focus indicator whenever :focus-visible is applied to one of its children. πŸŽ‰

This is perfect if you're building a complex custom component with a lot of nested elements that should only show a focus indicator to mouse users when it's really needed, and always show a focus indicator to a keyboard user, or a user of other assistive tech.

The full picture of focus selectors

All in all, here's a table that summarizes the focus pseudo-classes, how they're applied, and when they're helpful.

Pseudo-class/selectorFocus targetOnly applied when focus is helpfulOnly applied when ancestor has focus
:focusElement in focusNoNo
:focus-withinAncestor element in focusNoYes
:focus-visibleElement in focusYesNo
:has(:focus-visible)Ancestor element in focusYesYes

Here's a visual example of a <button> in a group similar to the table above.

A matrix showing focus, focus-within, focus-visible, and focus-visible-within

The dotted lines indicate that the focus ring will only appear when it's helpful to a non-mouse user.

Note that :focus-visible behaves differently based on the element it's applied to!

Check out this CodeSandbox to see how :focus-visible behaves on different elements.

A final tip for approaching focus styling

As a rule of thumb ask yourself, "If I tapped on this element when using a mobile device, would I expect the keyboard to appear?"

If your answer is yes, then it's extremely important to visibly show focus styles on the element.

In a lot of other cases, you should check if any of the other approaches you've just read about may be more suitable. Take care to make the user-experience delightful and clear without unnecessarily distracting people from achieving what they set out to do.

And if you're in doubt, you could always just show visible focus styles or leave the focus styles alone and let the browser handle it for you.

References

Go to posts