Skip to content

ally.js

JavaScript library to help modern web applications with accessibility concerns by making accessibility simpler

# 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:

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;
}

# 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