Hiding stuff with CSS

  • CSS
  • Hidden stuff

Happy Halloween! 🎃 Today I'm exploring hidden content in CSS, some might say ghost content. Quite spooky! There are lots of ways to hide stuff (and lots of reasons for doing so), so let's dig into some of them.

Display: none

display: none; is the classic way to hide stuff in CSS. It's sort of the most extreme method of doing so, basically telling the DOM to erase that content entirely. The hidden content won't be present in the DOM, it won't take up any space, and it's not available to Assistive Technology. It basically communicates to the browser, "For all intents and purposes, treat this content like it doesn't exist."

display: none; is especially powerful when combined with media queries - it allows you to toggle between mobile, tablet, and desktop variants quite effectively:

.navigation-desktop {
display: none;
}
.navigation-tablet {
display: none;
}
@media screen and (min-width: 768px) {
.navigation-mobile {
display: none;
}
.navigation-tablet {
display: block;
}
}
@media screen and (min-width: 1024px) {
.navigation-tablet {
display: none;
}
.navigation-desktop {
display: block;
}
}

This is a pretty standard way of implementing different designs of the same contents for different breakpoints without clogging up the user experience and accessibility tree with repetitive content.

Visibility: hidden

Where display: none; effectively removes an element and any space that element would take up out of the browser, visibility: hidden; visually hides an element, but the space that element would take up is still taken up. Essentially, it leaves a gap where the element should be. Somewhat related, where display: none; is not in the DOM at all, an element that is hidden with visibility: hidden; is still in the DOM. This is perhaps unsurprising, considering that element still takes up space.

This is really useful when you are toggling the visibility of some contents based on user interaction and you want to avoid layout shifts. For example, you might build a "spoilers" feature on a movie review website, or you might want to hide the answer to a question in an FAQ or a flash card component.

Like display: none;, elements hidden with visibility: hidden; are not visible to Assistive Technology. At the time of writing, one of these elements appears as ignored in the Chrome accessibility tree.

Similarly, elements hidden with visibility: hidden; aren't interactive. The following button will take up space, but won't be able to be clicked:

<style>
.visibility-hidden {
visibility: hidden;
}
</style>
<button class="visibility-hidden">Submit</button>

Hovering to reveal

I discovered some curious behavior pairing visibility: hidden; with hover state. I was attempting to swap from visibility: hidden; to visibility: visible; when the following text was hovered:

<style>
.visibility-hidden: {
visibility: hidden;
}
.visibility-hidden:hover {
visibility: visible;
}
</style>
<p class="visibility-hidden">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
</p>

However, it wasn't working. It turns out that even though the hidden element takes up space in the DOM, you can't hover over that hidden element. Weird. In order to make this work, you need to attach the :hover pseudoclass to a parent element to the hidden element:

<style>
.visibility-hidden: {
visibility: hidden;
}
.hover-to-reveal:hover .visibility-hidden {
visibility: visible;
}
</style>
<div class="hover-to-reveal">
<p class="visibility-hidden">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
</p>
</div>

Just a quirk to call out - of course, if we want to put the ability to show or hide content into the hands of all of our users, including those who don't use a mouse to navigate the web, we shouldn't rely solely on mouse hover.

Opacity: 0

The opacity CSS property gives us another way to adjust the visibility of content. However, where visibility: hidden; and visibility: visible; (and display: none; / display: block; for that matter) present are a hard-line binary for "you can't see this" and "you can see this", opacity lets us set a value between 0 and 1 (including decimal values). 0 makes an element fully transparent, effectively invisible; 1 makes an element fully opaque, effectively fully visible, and anything in between is translucent to that amount. .5 is 50% translucent, .33 is 33% translucent, and so on.

opacity only affects translucence. Like visibility: hidden;, elements hidden with opacity: 0; still take up space like they normally would and remain present in the DOM. However, unlike visibility: hidden;, elements hidden with opacity: 0; can be hovered. You can tie the opacity value directly to the :hover pseudoclass (and not have to tie it to a parent element):

.opacity-zero {
opacity: 0;
}
.opacity-zero:hover {
opacity: 1;
}

opacity: 0; elements can also be interacted with - buttons, while not visible, can still be navigated to via keyboard navigation or clicked; text can be selected; and these elements are visible to Assistive Technology. As such, care should be taken not to inadvertently introduce accessibility issues.

opacity is helpful when you want fine-grain control over how translucent an element is (though be careful not to make any crucial text too faint to read; always abide by color contrast guidelines), or if you want to animate an element's transition from invisible to visible.

Hiding screenreader content from sighted users

Closely related to what we've covered is the need to provide text content specifically for Assistive Technology users. Say, for example, you have a button whose sole contents is a magnifying glass SVG icon:

<button>
<MagnifyingGlassIcon />
</button>

For a sighted user, that magnifying glass communicates that this is a button related to some kind of search functionality. However, with nothing else provided, there's nothing to communicate "Search" to Assistive Technology. We could provide a <span> with some text to help out:

<button>
<MagnifyingGlassIcon />
<span>Search</span>
</button>

The button now has some text content, which makes Assistive Technology happy, but that "Search" word ruins our sleek design. What we need is a way to visually hide that <span> element from sighted users but include it for Assistive Technology. That's what the following CSS accomplishes:

.visually-hidden {
position: absolute;
overflow: hidden;
height: 1px;
width: 1px;
margin: -1px;
padding: 0;
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
}

Then we can apply it as follows:

<button>
<MagnifyingGlassIcon />
<span className='visually-hidden'>Search</span>
</button>

Alternatively, in this case we can utilize aria-label:

<button aria-label="Search">
<MagnifyingGlassIcon />
</button>

Hiding sighted user content from screenreaders

So now we know how to hide text intended only for Assistive Technology users from sighted users. Sometimes we want to do the opposite, for example if we have a purely decorative SVG image.

Where you can obscure decorative <img> elements by passing an empty alt attribute:

<img src="./image.png" alt="" />

You would do the same for an <svg> element with aria-hidden="true":

<svg aria-hidden="true" />

An optional best practice here would be to also pass focusable="false":

<svg aria-hidden="true" focusable="false" />

Summary

Hidden contentdisplay: none;visibility: hidden;opacity: 0;
is in the DOMNoYesYes
retains spaceNoYesYes
can be hoveredNoNo, apply hover to parentYes
can be interacted withNoNoYes
is visible to Assistive TechnologyNoNoYes

display: none;, visibility: hidden; and opacity: 0; are the main ways by which web content is hidden from sighted users (and sometimes also from non-sighted users). As you can imagine though, there are several other ways to do this. transform: scale(0);, clip-path: polygon(0 0, 0 0, 0 0, 0 0);, and height: 0; overflow: hidden; all offer similar flavors of the same stuff we've seen. These are generally not used as frequently, so I'm not detailing them.

With regards to hiding things in an accessible manner, the following will hide contents from sighted users but make it available to Assistive Technology:

.visually-hidden {
position: absolute;
overflow: hidden;
height: 1px;
width: 1px;
margin: -1px;
padding: 0;
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
}

And aria-hidden="true" (or an empty alt attribute on an <img> element) will hide decorative elements from Assistive Technology, but still present it to sighted users.