You would think that hiding content with CSS is a straightforward and solved problem, but there are multiple solutions, each one being unique.
Developers most commonly use display: none
to hide the content on the page. Unfortunately, this way of hiding content isn’t bulletproof because now that content is now “inaccessible” to screen readers. It’s tempting to use it, but especially in cases where something is only meant to be visually hidden, don’t reach for it.
The fact is that there are many ways to “hide” things in CSS, each with their pros and cons which greatly depend on how it’s being used. We’re going to review each technique here and cap things off with a summary that helps us decide which to use and when.
How to spot differences between the techniques
To see a difference between different ways of hiding content, we must introduce some metrics. Metrics that we’ll use to compare the methods. I decided to break that down by asking questions focused on four particular areas that affect layout, performance and accessibility:
- Accessibility: Is the hidden content read by a screen reader?
- Document flow: Will the hidden element affect the document layout?
- Rendering: Will the hidden element’s box model be rendered?
- Event triggers: Does the element detect clicks or focus?
Now that we have our criteria out of the way, let’s compare the methods. Again, we’ll put everything together at the end in a way that we can use it as a reference for making decisions when hiding things in CSS.
Method 1: The display
property
We kicked off this post with a caution about using display
to hide content. And as we established, using it to hide an element means that the element is not generated at all. It’s in the DOM, but never actually rendered.
The element will still show in the markup, if you inspect the page you will be able to see the element. The box model will not generate nor appear on the page, which also applies to all its children.
And what’s more, if the element has any event listeners — say a click or hover — they won’t register at all. And as we’ve discussed already, all the content will be ignored by screen readers. Here, we have two visible buttons and one hidden with display: none
. All three buttons have click events but only the two visible buttons will render and register the clicks.
Display is the only property that will affect image request firing. If an image tag (or parent element) has a display
property set to none
either through inline CSS or by selector, the image will be downloaded. On the other hand, if the image is applied with a background
property, it won’t be downloaded.
This is the case because the parser hasn’t applied the CSS when an HTML document is parsed and it encounters an <img>
tag. On the other hand, when we apply the image to an element with a background
property, the image won’t be downloaded because the parser hasn’t applied the CSS where the image is called. This behavior is matched across all latest browsers. The only exception is IE 11, which will download images in both cases.
Metric | Result |
---|---|
Is the hidden content read by a screen reader? | ❌ |
Will the hidden element affect the document layout? | ❌ |
Will the hidden element’s box model be rendered? | ❌ |
Does the element detect clicks or focus? | ❌ |
Method 2: The visibility
property
If an element’s visibility
property is set to hidden
, then the element is “visually hidden.” Being “visually hidden” sounds a lot like what display: none
does, but it’s incredibly different in that the element is generated and rendered, but invisible. This means that the element’s box model is present, giving it dimensions that continue to occupy space on the screen even though it doesn’t appear to be there.
Imagine you’re wearing an invisible cloak that makes you invisible to others, but you are still able to bump into things. You’re physically there, even if you’re invisible to the human eye.
But that’s where the differences between “visually hidden” and “not displayed” end. In fact, elements hidden with visibility
and display
behave the same in terms of accessibility and event triggers. Invisible elements are inaccessible to screen readers and won’t register events, as we see in the following demo that’s exactly the same as the last example, but merely swaps display: none
with visibility: hidden
.
Metric | Result |
---|---|
Is the hidden content read by a screen reader? | ❌ |
Will the hidden element affect the document layout? | ✅ |
Will the hidden element’s box model be rendered? | ✅ |
Does the element detect clicks or focus? | ❌ |
Method 3: The opacity
property
The opacity
property only affects the visual aspect of the element. If we set an element’s opacity
to zero, the element will be fully transparent. Again, it’s a lot like visibility: hidden
where we’re draping an invisible cloak on the element where it’s invisible, but still physically present.
In other words, what we have is a hollow, transparent element that acts like any other element, only it’s invisible. Sounds a lot like the visibility
method, right? The difference is that a fully transparent element is still accessible to a screen reader and can register events, like clicks, as we see in the following example.
Metric | Result |
---|---|
Is the hidden content read by a screen reader? | ✅ |
Will the hidden element affect the document layout? | ✅ |
Will the hidden element’s box model be rendered? | ✅ |
Does the element detect clicks or focus? | ✅ |
Method 4: The position
property
Pushing an element off-screen with absolute positioning is another way developers often hide things. Using top
and left
, we can push the element so far off the screen that there’s no way it will ever be seen. It’s like hiding the cookie jar outside of the house so the kids (or maybe you!) can’t find them.
“Absolute” is the key word here. If we set position
to absolute
, an element is taken out of the document flow which is a way of saying it no longer adheres to its natural position in the DOM. In other words, the page doesn’t reserve any space for it, which knocks the element out of order visually, positioning it to it’s nearest positioned element if there is one, or the document root if nothing else.
We take advantage of absolute positioning by taking the “hidden” element out of the document flow and offsetting it toward the top-left with values of -9999px
.
.hidden {
position: absolute;
top: -9999px;
left: -9999px;
}
Metric | Effect |
---|---|
Is the hidden content read by a screen reader? | ✅ |
Will the hidden element affect the document layout? | ❌ |
Will the hidden element’s box model be rendered? | ✅ |
Does the element detect clicks or focus? | ✅ |
If the hidden element contains focusable content, the page will scroll to the element when it is in focus, creating a sudden jump.
Method 5: The “visually hidden” class
So far, the position
method is the closest we’ve seen to an accessibility-friendly way to hide things in CSS. But the problem with focusable content causing sudden page jumps isn’t great. Another approach to accessible hiding combines absolute positioning, the clip
property and hidden overflow. Scott O’Hara blogged it back in 2017.
.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Let’s break that down.
We need to remove the element from the document flow. The best way to do this is by using position: absolute
. This will remove the element, but we won’t push it off the screen.
.visually-hidden {
position: absolute;
}
We can hide the element by setting the width and height property to zero. Unfortunately, that won’t work because some screen readers will ignore elements with zero width and height. What we can do is set it to the second-lowest value, 1px
. That means the content will easily overflow the space, so we also need overflow: hidden
to make sure it doesn’t visually spill over.
.visually-hidden {
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
To hide that one-pixel square, we can use the CSS clipping property. It is perfect for this situation, as it doesn’t affect screen readers. The content is there but, again, is visually hidden. The thing to note is that clip
was deprecated in favor of clip-path
but is still needed if we need to support older versions of Internet Explorer.
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
Another piece of the “visually hidden” class puzzle is to address smushed off-screen accessible text, an oddity that removes white-spacing between words, causing them to be read aloud like one big string of words. For example, “Welcome back home” will be read out as “Welcomebackhome.”
A simple solution to this problem is to set the white-space: nowrap
:
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
And, finally! The last thing to consider is to allow certain element with native focus and active sites to display when they are in focus, while continuing to prevent other elements, like paragraphs, from displaying. We can use the :not
pseudo-selector for that.
.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Metric | Result |
---|---|
Is the hidden content read by a screen reader? | ✅ |
Will the hidden element affect the document layout? | ❌ |
Will the hidden element’s box model be rendered? | ❌ |
Does the element detect clicks or focus? | ✅ |
Honorable mentions
There are even more methods than the five we’ve covered. For example, the text-indent
property can push text off the screen like the position
method:
.hidden {
text-indent: -9999em;
}
Unfortunately, this approach doesn’t jive with RTL writing modes. That makes it less adaptable than other solutions we’ve covered.
Another method is using transform
to scale or move the element out of the way. It works the same — visually only — like opacity
.
.hidden {
transform: scale(0);
}
Let’s put everything together!
We got to a solution that will visually hide content but still be accessible. Then, should you stop using display: none
? No, this is still the best way to hide an element completely (visually and accessibly).
That said, It is worth mentioning that if you want to achieve an opposite result — hide something from the screen reader, the aria-hidden="true"
attribute will hide the content from screen readers, but not visually.
With that, here is a complete table that compares all of the approaches. Use it to guide your decisions on how to hide content next time you find yourself in that situation.
Metric | Display | Visibility | Opacity | Position | Accessible Way |
---|---|---|---|---|---|
Is the hidden content read by a screen reader? | ❌ | ❌ | ✅ | ✅ | ✅ |
Will the hidden element affect the document layout? | ❌ | ✅ | ✅ | ❌ | ❌ |
Will the hidden element’s box model be rendered? | ❌ | ✅ | ✅ | ✅ | ❌ |
Does the element detect clicks or focus? | ❌ | ❌ | ✅ | ✅ | ✅ |
Source: CSS-tricks.com