Comparing Various Ways to Hide Things in CSS
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
- Accessibility: Is the hidden content read by a screen reader?
- Documentflow: 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 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 .
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)
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.
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.
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.
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 :
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)
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? | ✅ |
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:
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 .
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? | ❌ | ❌ | ✅ | ✅ | ✅ |
visibility
The visibility CSS property shows or hides an element without changing the layout of a document. The property can also hide rows or columns in a .
Try it
To both hide an element and remove it from the document layout, set the display property to none instead of using visibility .
Syntax
/* Keyword values */ visibility: visible; visibility: hidden; visibility: collapse; /* Global values */ visibility: inherit; visibility: initial; visibility: revert; visibility: revert-layer; visibility: unset;
The visibility property is specified as one of the keyword values listed below.
Values
The element box is visible.
The element box is invisible (not drawn), but still affects layout as normal. Descendants of the element will be visible if they have visibility set to visible . The element cannot receive focus (such as when navigating through tab indexes).
The collapse keyword has different effects for different elements:
- For rows, columns, column groups, and row groups, the row(s) or column(s) are hidden and the space they would have occupied is removed (as if display : none were applied to the column/row of the table). However, the size of other rows and columns is still calculated as though the cells in the collapsed row(s) or column(s) are present. This value allows for the fast removal of a row or column from a table without forcing the recalculation of widths and heights for the entire table.
- Collapsed flex items and ruby annotations are hidden, and the space they would have occupied is removed.
- For other elements, collapse is treated the same as hidden .
Accessibility concerns
Using a visibility value of hidden on an element will remove it from the accessibility tree. This will cause the element and all its descendant elements to no longer be announced by screen reading technology.
Interpolation
When animated, visibility values are interpolated between visible and not-visible. One of the start or ending values must therefore be visible or no interpolation can happen. The value is interpolated as a discrete step, where values of the easing function between 0 and 1 map to visible and other values of the easing function (which occur only at the start/end of the transition or as a result of cubic-bezier() functions with y values outside of [0, 1]) map to the closer endpoint.
Notes
- Support for visibility: collapse is missing or partially incorrect in some modern browsers. It may not be correctly treated like visibility: hidden on elements other than table rows and columns.
- visibility: collapse may change the layout of a table if the table has nested tables within the cells that are collapsed, unless visibility: visible is specified explicitly on nested tables.