Catalin Ciubotaru avatar

CSS tidbits - How to hide an element on focus lost

For those moments when you want to build a search, with autocomplete, but you don’t want to use JavaScript for the suggestions.

The Problem

Let’s say you’re tasked with building this new Search 🔎 component, but to show your CSS prowess, or just to be more efficient, you want to manage the show/hide state of the suggestions just with CSS. After all, it doesn’t make sense write a whole focus-lost directive for this.

Good to know

Not much, this post relies only on HTML and CSS knowledge 📚.

The Solution

We’ll split this into a couple of steps:

  1. Build the HTML of the component
  2. Add some basic CSS styling
  3. The magic touch 🧙‍♂️

Let’s go!

Part 1 - Build the HTML of the component

We need our building blocks. What’s a search? It’s an input. So, we start with this:


<input
  type="text"
  placeholder="search..."
/>

Nothing fancy here. A simple input, with a type and some placeholder text.

Next to this, we’ll need a place to put all our search results. The result could be something like this:


<div>
  <input
    type="text"
    placeholder="search..."
  />
  
  <ul>
    <li>Batman</li>
    <li>Joker</li>
    <li>Harley Quinn</li>
    <li>Nightwing</li>
    <li>Bane</li>
  </ul>
</div>

And that’s it. One small note, we’ve put everything inside a div, to have it wrapped nicely.

Part 2 - Add some CSS styling

Now, let’s make this subjectively ugly thing, a bit prettier. We’ll start with the input.


input {
  background-color: grey;
  border-radius: 24px;
  border: 1px solid lightgrey;
  padding: 1em;
  color: white;
  outline-offset: 4px;
  width: 100%;
}

input::placeholder {
  color: lightgrey;
}

A couple of things to notice here:

  • We’ve added “nice” colors to the text, border and placeholder.
  • We’ve added some spacing.
  • We’ve added some rounded borders, to make our input more pleasant to look at.
  • We’ve increased the outline-offset for when the input is focus, to make sure it’s clear for people using assistive technology.

Now, let’s add some styling to our list of results


ul {
  background-color: grey;
  list-style: none;
  margin: 0;
  width: 100%;
  padding: 1em;
  max-height: 300px;
  overflow-y: auto;
}

To note, here, besides the color and the spacing:

  • We’ve set the list-style to none, to get rid of the ⚫️ in front of each item.
  • We’ve set a maximum height to 300px. In case our search has to many results, we’re limiting the amount of space they take.
  • This is combined with an overflow-y of auto to take care of adding a scroll bar if there are too many results.

Finally, some basic styling for each result item:


li {
  overflow: hidden;
  white-space: nowrap;
  padding-block: 0.5em;
  color: white;
}

Here we’re making sure each value does not wrap, to take more than one row. It will simply vanish on the right (or left, depending on your locale). This is just a choice, feel free to do something magical ✨ here.

With the styling done, we need to properly place the results under the search input. This is done easily with absolute positioning.


div {
  position: relative;
  width: 250px;
}

ul {
  position: absolute;
  z-index: 1;
  ...other properties
}

Here we’re making sure the container has relative positioning, and within it, we’re placing our list with position: absolute;. You’ll notice that there are no adjustments to the place it sits in. There’s not top, right, bottom or left property. This is because we’ve already set the width to be 100%, and the height will be as much as needed, up to a maximum of 300px.

Important to note is the z-index as well. We want to make sure our results will sit on top of our content.

Easy peasy 🫛 right? With this, we have a “fully-styled” search component 🔎.

Now, for the last bit.

Part 3 - The magic touch 🧙‍♀️

I feel like I’ve built this up too much. It’s not THAT magical.

So, we want to type something in our input, see some results, and maybe click outside our input or outside our list. When this happens, the results list should be hidden. Right?

:focus-within to the rescue 🚑!


div:not(:focus-within) ul {
  display: none;
}

Looks like a weird selector, but let me walk you through it.

  1. First, we’re selecting our div wrapper.
  2. Then, we’re putting a condition on it not having focus within. As in, no child of this div wrapper is focused.
  3. Then, when this condition is fulfilled, we select the ul inside the div.
  4. Last but not least, we hide this ul element.

In other words, we could rephrase it like this:

Select the list that lives inside a div element which has no focused child

Hope this makes more sense 🧐

See, it wasn’t that magical 🪄. Just a super nice CSS property :focus-within

Here’s how the final result looks like:

Demo
  • Batman
  • Joker
  • Harley Quinn
  • Nightwing
  • Bane

Also, here's a link to a working StackBlitz.

Congrats 🎉

You made it yet through another article. Thanks for spending the time reading this, and hope it provided some sort of value, since time is a limited resource. Use it wisely. 🙏

Want more?

If you want to know more about this, or something doesn't make any sense, please let me know.

Also, if you have questions, you know where to find me… On the internet!

Be kind to each other! 🧡

Over and out

My Twitter avatar
Catalin Ciubotaru 🚀

It's been a while ⏰, I know, but here's a new short post on how to hide an element on focus lost without using JavaScript 🚀: https://catalincodes.com/posts/css-tidbits-how-to-hide-element-on-focus-lost #HTML #CSS #UI

Sep 24, 2023
37 people are talking about this

Wanna read more?

Updates delivered to your inbox!

A periodic update about my life, recent blog posts, how-tos, and discoveries.

No spam - unsubscribe at any time!