# Hiding DOM elements
This document explains the various ways of hiding things and the implications that come with that.
When we say an element is hidden, we usually mean it is not visible. However, the screen is not the only output mechanism we may need to hide content from. Systems like screen readers and braille displays rely on a document's representation in the accessibility tree. For disambiguation we'll use the following terms:
- Completely hidden
- The element is not rendered on screen, not exposed in the accessibility tree, not accessible to keyboard navigation.
- Semantically hidden
- The element is rendered on screen, but not exposed in the accessibility tree, and still accessible to keyboard navigation.
- Visually hidden
- The element is not rendered on screen, but exposed in the accessibility tree, and still accessible to keyboard navigation.
The three types of "hidden" produce the following matrix:
visibility state | on screen | in accessibility tree | keyboard navigation |
---|---|---|---|
completely hidden | hidden | hidden | not navigatable |
semantically hidden | visible | hidden | navigatable |
visually hidden | hidden | visible | navigatable |
# How to hide elements completely
Completely hiding elements can be done in 3 ways:
- via the CSS property
display
, e.g.display: none;
- via the CSS property
visibility
, e.g.visibility: hidden;
- via the HTML5 attribute
hidden
, e.g.<span hidden>
While each of these techniques has the same end result, i.e. content is not rendered and not exposed in the accessibility tree, they have different behaviors.
# The CSS properties display
and visibility
display: none;
will cause the element to completely vanish, it won't take any space and it won't be animatable. visibility: hidden;
allows animation and preserves the space the element would occupy on screen, but simply leave it blank. Unlike every other method to hide content, visibility
has the ability to unhide nested content:
<div style="visibility: hidden">
<span>not visible</span>
<span style="visibility: visible">visible!</span>
</div>
Unless unhiding nested content is specifically what we intend to do, we should refrain from using visibility: visible;
in our style sheets, and use visibility: inherit;
instead. By inheriting the visibility
state from the parent element by default, we make sure that nested content does not become visible by accident, in case the an ancestor has visibility: hidden;
set.
# The HTML5 hidden
attribute
The HTML5 hidden
attribute provides a convenient API, as it can be toggled simply by setting element.hidden = true;
. The element itself does not hide the content, but browser's internal style sheets contain the following CSS rule:
[hidden] {
display: none;
}
# Safely overwriting the hidden
attribute
We absolutely must not revert the hiding effects of the hidden
attribute. However, we can swap display
for visibility
in certain situations, for example to allow us to animate the element:
.my-element[hidden] {
display: block;
visibility: hidden;
}
Sadly we cannot use the values inherit
, initial
or unset
to simply undo the display: none;
. initial
and unset
would translate to display: inline
, and inherit
simply "imports" the display
value of the parent element. We can use extended selectors to get around duplicate definitions:
.my-element,
.my-element[hidden] {
display: block;
/* ... */
}
.my-element[hidden] {
visibility: hidden;
}
# How to hide elements semantically
To hide content from the accessibility tree but retain the content on screen, we may use the attribute aria-hidden="true"
.
For example we might want to hide certain images and icons that serve non-descriptive, purely esthetical purposes:
<a href="https://google.com">
<img src="search-symbol.png" alt="" aria-hidden="true">
Google Search
</a>
We should not add any visual styles (like visibility: hidden;
or display: none;
) to the CSS selector [aria-hidden="true"]
, as this would make us lose the ability to hide content only from screen readers.
# How to hide elements visually
We may need to provide invisible content in order to make sure the structures presented in the accessibility tree make sense. The following CSS is taken from HTML5 Boilerplate, which is based on Hiding Content for Accessibility:
.visuallyhidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
The CSS property clip
is supported in every browser, but was deprecated in CSS Masking Level 1. Instead we're supposed to use clip-path
, which isn't widely supported yet. To support this swap of CSS properties - whenever that may happen - we would add clip-path: inset(100%);
to cover the deprecated clip: rect(0 0 0 0);
style.
While Internet Explorer 9 - 11 make overflowing containers focusable, the clip
makes sure the outline
drawn by :focus
is not visible and the element cannot be clicked on.
Speaking of focusable elements, we are likely using the .visuallyhidden
style for skip links, in which case we need a way to undo the visual hiding. The HTML5 Boilerplate provides the following styles for that:
.visuallyhidden.focusable:active,
.visuallyhidden.focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
clip: auto;
overflow: visible;
}
This approach poses a couple of problems, though. First of all we need to declare elements compatible by adding the class focusable
. Second - and much more importantly - we're resetting styles to values that are likely not what we intend to render. Instead we could use :not()
, which is supported in every modern browser.
According to Beware smushed off-screen accessible text the above snippet of CSS might pose a problem to some screen readers. It seems that (in some situations) separate words might be concatenated and thus spoken weirdly. Luckily we can prevent the undesired collapse of whitespace by adding white-space: nowrap;
.
# 2017 edition of .visuallyhidden
Putting all of this together we get the following styles to visually hide content:
.visuallyhidden:not(:focus):not(:active) {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
white-space: nowrap;
clip-path: inset(100%);
clip: rect(0 0 0 0);
overflow: hidden;
}
- It works in all modern browsers including Internet Explorer 9 - 11.
- It side-steps the need to re-style everything for focusable elements such as skip-links.
- It accounts for the deprecated
clip
property.
# Keyboard navigation
The techniques to hide elements only visually or semantically come with a caveat. Focusable elements like <a href="…">
remain keyboard navigatable, even though the element is not visible on screen or not exposed in the accessibility tree.
To make sure sighted keyboard users do not end up focusing elements they can't see, and screen reader users not focusing element's that don't exist for them, we need to make sure that partially hidden content is not accessible to keyboard navigation using the Tab and Shift Tab keys. To accomplish this, we can add tabindex="-1"
to the elements we want to hide from the keyboard.
# Recap
- Use the
hidden
attribute to completely hide an element. - Use the
aria-hidden
attribute to hide an element from the accessibility tree. - Use the
.visuallyhidden
class to hide an element from the screen. - Use
visibility: inherit;
instead ofvisibility: visible;
to avoid accidentally showing content. - Do not attach any CSS styles to the
aria-hidden
attribute. - Take care of keyboard focusable content that is partially hidden by adding
tabindex="-1"
.