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.
display
property
Method 1: The 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? | ❌ |
visibility
property
Method 2: The 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? | ❌ |
opacity
property
Method 3: The 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? | ✅ |
position
property
Method 4: The 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? | ❌ | ❌ | ✅ | ✅ | ✅ |
Position and lower than 0 z-index can be another way to hide elements on the web page.
That is true. But there are a lot of caveats to z-index, this is why people avoid it. For example, adding a
transform
oropacity
will put the element in its own new stacking contextOr grid and z-index.
Same trick can be used for https://css-tricks.com/how-to-create-a-skip-to-content-link/
I don’t understand the accessibility problem with display:none method.
It says : “hidden content is not read by a screen reader”.
OK.
Isn’t that the purpose?
Yes, but not in all cases. That’s sorta the point here: there are different ways to do it and each has implications that might be better in certain situations.
We usually jump to this conclusion. Because we have a perfect vision and can see the context around a said item. On the other hand, users who are blind or have other accessibility problems won’t be aware of this.
A great example of this is with labels on inputs. We usually either hide them on responsive or don’t include them at all, and that is wrong. We should hide them only visually but still keep them available to the screen readers.
This post explores different ways to hide something and what are the results of this action.
99% of cases:
manager 1: i need a button
manager 2: function is not ready, hide it, don’t break layout
screen reader: H-E-L-L-O, button. CLICK ME. CLICK ME. CLICK ME.
Love this… Unfortunately, true…
Very interesting post!
You can also hide something by giving it a height and width of 0px and sometimes you need to set the padding or margin to 0px as well.
Be aware that (most) screen readers will not read elements with 0 widths and height.
Hidden or display:none is used a lot for responsive behavior. But it doesn’t help the mobile user in terms of page download speed because the elements are still downloaded (is: images). Isn’t there a way to hide something completely? Like javascript .remove so the browser can load content faster for mobile users?
As stated in the article if you have a
background-image
it won’t download. On the other hand,img
will download.What you are asking is kind of impossible because you would need to work in the other direction.
For an image/s to not be downloaded they would need to be skipped by the HTML parser. This means you need to remove them before the parser goes over them. This is not possible because you would need to select that element in JS and remove it, right? But you can’t select somethinging that the parser didn’t attach to the DOM tree.
Adding a
<script>
in the<head>
won’t work because the above, the document hasn’t been parsed yet. Adding it at the bottom of<body>
will be too late, because it has already been parsed and image requests are sent.To achieve this you have to think the other way around, adding elements not removing them. What you can do is on page load check the resolution with a JavaScript Media Query and add the element if needed.
The closest thing I found to do something similar to what you’re asking is using the picture element with srcset and using a base64 image for the smallest size. Look at this link for more detail. I hope it helps!
https://sjardo.com/disable-loading-images-on-mobile/
And the HTML attribute hidden does what?!
The article title is “Comparing Various Ways to Hide Things in CSS” but yes, the HTML attribute
hidden
automatically appliesdisplay: none
to the element in browsers that support it. For browsers that don’t recognize thehidden
attribute, have CSS like this site does:[hidden] { display: none }
.Hey why does the
clip: rect(0 0 0 0);
work to hide the content? I would expect a clipping rectangle with a zero offset to not clip anything. I played with this in a codepen and it seems thatclip
will hide the element it’s applied to no matter what the clipping offset is and irrespective of the box’s dimensions, padding, or border which doesn’t make any sense to me. Guess it’s a good thing they deprecated it.If it’s just text you are hiding like this
<div class="logo">
<svg>...</svg>
<a href="/">Home Page</a>
</div>
I often use
.logo {position:relative;}
.logo a {position:absolute !important; left:0; right:0; top:0; bottom:0; font-size:0}
Everything is still tabable, in the normal flow, it’s just the text size is too small to see. Can anyone think of any issue with that in current modern browsers?
What is the best performance method if you have a javascript midi sequencer, start it, and then switch to a track control (
…controlls…
) ?
With display: Block there is a timing dropout :(
Or is it better to create and toggle the part control with a canvas element (Part steps need checkboxes per step to play a sample (How is the checkbox reading performance. All checkboxes of all parts must be readable )) ?
Does somebody has any idea?
Thanks very much !