Enhancing focus visibility - focus-within or has(:focus)?
Tags: css, a11y, webdev, tutorial
Published: 2023-08-09 04:04:04

---
title: "Enhancing focus visibility - focus-within or has(:focus)?"
published: true
description: "Discover how to use the :focus-within & :has() pseudo-classes in CSS to style parent elements based on a child's focus state!"
cover_image: "https://res.cloudinary.com/mayashavin/image/upload/v1691498641/articles/CSS/focus-within-cover.png"
tags: ['css', 'accessibility', 'webdev', 'tutorial']
canonical_url: https://mayashavin.com/articles/focus-within-vs-has-focus
series: Efficient CSS
---

_Previously we have seen how to style an element in focus mode with CSS `:focus` and `:focus-visible` pseudo-classes for better accessibility. What if we want to style a parent element when its child is on focus and make it more standout for the user's visibility on a crowded page? What are the options for us to achieve this?_

_Let's find out in this article, starting with the `:focus-within` pseudo-class._

## Table of Content

- [Table of Content](#table-of-content)
- [Using :focus-within pseudo-class](#using-focus-within-pseudo-class)
- [Using :has() pseudo-class for focus within](#using-has-pseudo-class-for-focus-within)
- [When to use :focus-within and :has(:focus)?](#when-to-use-focus-within-and-hasfocus)
- [Summary](#summary)

## Using :focus-within pseudo-class

While the `:focus` pseudo-class is for selecting an element in focus mode, the `:focus-within` is for selecting that element's ancestor in the DOM tree. In short, `:focus-within` will match any element on which one of its nesting elements is focused (matching  `:focus`).

Let's look at the following example of a search box where we have two levels of nesting. The first level is the `div` with a class of `search-container`, and the second is the `label` element.

```html
<div class="search-container">
    <label>
        Search a title
        <input 
           type="text" 
           placeholder="Search" 
           id="search-box" 
         />
    </label>
    <div>
        <button class="clear-btn">Clear</button>
        <button class="search-btn">Search</button>
    </div>
</div>
```

We then add some outline styles to `:focus-within` of the top level `div` with the class `search-container`, and font's boldness to `:focus-within` of the `label` element, as follows:

```css
.search-container:focus-within {
    outline: 2px solid rgba(66, 153, 225, 0.5);
    outline-offset: 5px;
}

label:focus-within {
    font-weight: 600;
}
```

With that, when we focus on the `input` element within `label`, the label text becomes bold, and the outline of the top level `div` with class `search-container` becomes visible. And when we focus on the buttons, either by clicking on them or by using the Tab key, we will see that only the top-level `div` with class `search-container` changes its outline style:

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto,f_auto/v1690881233/articles/CSS/focus-within.gif" loading="lazy" alt="Different styles when focusing on input, and when focusing on buttons" class="mx-auto"/>

To debug the styles for `:focus-within`, we can head to the DevTools, inspect the desired element and toggle the element's `:focus-within` state listed under the `Styles` tab - `:hov` section. We can also toggle the `:focus` state of its children following the same approach and see how the parent's styles change:

<img src="https://res.cloudinary.com/mayashavin/image/upload/q_auto,f_auto/v1690881233/articles/CSS/debug-focus-within" loading="lazy" alt="Toggle focus-within state in the dev tools" class="mx-auto"/>

Now that we understand the `:focus-within` pseudo-class, let's see if we can achieve the same result with the `:has()` pseudo-class next.

## Using :has() pseudo-class for focus within

The `:has()` is a functional pseudo-class that accepts a list of relative selectors as input. It allows us to select and style an element if that element has a child (or a descendant) that matches the given list of selectors using the following syntax:

```css
:has(<selectors-list>) {
    /* styles */
}
```

For example, using our previous search box, we want to style the top level `div` with class `search-container` if it has a child element that matches the following conditions:

* The child has a class name, `clear-btn` and
* The child is **focus** mode

We can use `:has()` pseudo-class to achieve the above task as follows:

```css
.search-container:has(.clear-btn:focus) {
    outline: 2px solid rgba(66, 153, 225, 0.5);
    outline-offset: 5px;
}
```

And that's all it takes. Now only when we focus on the `Clear` button will we see the top level `div` with class `search-container` changes its outline style, leaving the rest of the elements' styles untouched:

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto,f_auto/v1691496044/articles/CSS/has-pseudo-clear.gif" loading="lazy" alt="Changing outline of the parent on button focused only" class="mx-auto"/>

Additionally, we can also pass multiple selectors to `:has()` pseudo-class, such as targeting the focus state of both the `Clear` button and the `Search` button:

```css
.search-container:has(.clear-btn:focus, .search-btn:focus) {
    outline: 2px solid rgba(66, 153, 225, 0.5);
    outline-offset: 5px;
}
```

Great. So far, we have seen how we can style a parent element based on the focus state of its children using `:focus-within` and `:has()` pseudo-classes. Here comes the question, which one should we use and when?

## When to use :focus-within and :has(:focus)?

Styling a parent element when one of its children is in focus can significantly improve visibility besides `:focus` and `:focus-within`. When navigating with a keyboard in a long list of components, such as a list of product cards, articles, etc., adding extra style to the parent element can help users quickly identify the focused part and its container, hence understanding the context better.

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto,f_auto/v1690881233/articles/CSS/focus-within-long-list.gif" loading="lazy" alt="Changing outline of the parent on button focused only" class="mx-auto"/>

Generally, using `:focus-within` pseudo-class is the most straightforward way to achieve the above task. However, `:focus-within` matches the focus state of **any of its descendants**, which means that if we have multiple descendants in focus mode, the ancestor will also change its styles. And this is not always the desired behavior.

Take our search box, for instance. When the search input is on focus, we may only want to highlight the label of the search box but not add any extra style to the top level `div`. And when one of the two buttons _Clear_ and _Search_ is in focus, we highlight the container `div`. With just `:focus-within`, it is **impossible** to achieve both scenarios.

In such cases, using `:has(:focus)` on a specific container or adding an explicit selector to `:has()` like `:has(#search-box:focus)` can be a better solution.

However, there is a performance catch here. Depending on how complex the relative selectors passed to it, `:has()` can be costly as the CSS engine needs to query all the matching elements for style calculation. In general, `:focus-within()` is faster than `:has(:focus)`, though the difference may not be critical. Below are the screenshots of performance tests for both pseudo-classes when adding to the same element `li` within a list:

* With `:focus-within` pseudo-class

<img src="https://res.cloudinary.com/mayashavin/image/upload/q_auto,f_auto/v1690881233/articles/CSS/perf-focus-within" loading="lazy" alt="Toggle focus-within state in the dev tools" class="mx-auto"/>

* With `:has(:focus)` pseudo-class
  
<img src="https://res.cloudinary.com/mayashavin/image/upload/q_auto,f_auto/v1690881233/articles/CSS/perf-has-focus" loading="lazy" alt="Toggle focus-within state in the dev tools" class="mx-auto"/>

Though the performance difference in the above test may not be significant, it is worth keeping in mind when we have a complex DOM structure and whether we need to specify a children's focus state or a regular _"catch them all"_ like `:focus-within` will be enough.

Lastly, the `:has()` functional pseudo-class is not supported in Firefox yet, so you may want to use `:focus-within` or add a fallback instead.

## Summary

In this article, we have learned how to use two pseudo-classes, `:focus-within` and `:has()`, to style a parent element based on the focus state of its children. We have also seen the difference between them and when one can be a more suitable choice than the other. While the accessibility impact can sometimes be subtle and not obvious, like direct focus style, it is always an excellent consideration to add a bit of visibility to the parent element, like a list item with interactive components inside. Users with keyboard navigation will appreciate you for that.

👉 _Learn about Vue with my new book [Learning Vue](https://www.oreilly.com/library/view/learning-vue/9781492098812/). The early release is available now!_

👉 _If you'd like to catch up with me sometimes, follow me on [Twitter](https://twitter.com/MayaShavin) | [Facebook](https://www.facebook.com/mayashavin/) | [Threads](https://www.threads.net/@mayashavin)._

Like this post or find it helpful? Share it 👇🏼 😉