Jump to main content

Inclusively Hidden

  • Published:
  • Category: Accessibility

Purposefully and appropriately hiding content from users

There are various techniques to visually hide content in our web interfaces, but are you aware of the different effects they have on the accessibility of that content? While it would be nice if there was a single, native, solution for hiding content, there are contextual benefits to the various techniques at our disposal.

Since there have been many articles already written about these techniques, over the many years they’ve been in use, the focus of this article will be to highlight the ones that are most appropriate for modern web development. We won’t just look at the code behind each of these techniques, instead we’ll focus on why each technique has its place, using practical examples to demonstrate their purposes.

But before we talk about how to hide content we should ask ourselves a question…

Why are we hiding content?

There are three reasons behind hiding content in an interface, and it’s important to identify what those reasons are, as they will correlate with the appropriate technique needed to hide such content.

  1. Temporarily Hidden Content:

    This sort of hidden content is part of the intended design and UX of an interface.

    There are many components in our interfaces (tab panels, off-screen navigations, modal windows, etc.) that are initially hidden until a state change occurs to bring these components, and their content, into view. Typically these sorts of initially hidden components are meant to be purposefully inaccessible until a requirement is met and the component becomes available to everyone.

  2. Purposefully Visually Hidden Content:

    To address a design's contextual shortcomings, hidden content may be added to components that have inherent visual meaning, but may otherwise be undiscoverable or confusing to users of assistive technology (ATs).

    As the best interfaces are interfaces that you don't even notice, removing extraneous visual elements and paring components down to their absolute necessities can go a long way to create seamless user experiences.

    However by striving for a minimal visual footprint, some of the first bits of "fluff" to be removed from an interface are often things like labels, or explicit copy.

    By re-injecting visually hidden (but screen reader friendly) content, like text to accompany elements only represented by icons, or additional context to "read more" links, one can significantly bridge the gap between how sighted users and users relying on assistive technology will experience your interfaces.

    In contrast to temporarily hidden content, this sort of hidden content is not meant to become visually accessible, because it is often added to make up for the fact it was absent from the visual interface.

  3. Purposefully Visual-Only Content:

    In very specific use cases, there may be visual elements that should not be announced to users of assistive technology. While this may seem counter intuitive to creating fully accessible user experiences, the goal with hiding this sort of content is to reduce content redundancies, and not to hide content that is important for all users.

Techniques for properly hiding content

Now that we’ve identified three categories of hidden content, the following are techniques one can utilize to appropriately hide each content type:

Hiding Content Completely

The techniques for hiding content completely are best used in conjunction with temporarily hidden content. This content should be hidden from all users until the content is needed. To achieve this, we can utilize the following:

  1. CSS display: none
    Useful for completely removing elements from the normal DOM flow. Content set to display none will not be accessible to any user.

  2. HTML Attribute [hidden]
    The hidden attribute is a natively semantic way to hide content with HTML alone. It mimics the display: none; declaration in browsers that support it (IE11+), and by declaring [hidden] { display: none; } in your CSS, even browsers that don’t natively recognize the attribute’s semantics, will appropriately hide it.

  3. CSS visibility: hidden
    Much like display none, this declaration will completely hide content from all users, with two main differences. First, this method does not remove the content from the normal DOM flow, so it’s physical space is still retained in the document. The second difference is that, unlike display: none, visibility: hidden can be transitioned with CSS, making it the preferred choice to hide elements that utilize CSS transitions when revealing content to the user. So to best utilize this technique, it should be paired with other CSS properties that affect the box model, to completely remove any trace of it from the visual interface, until it’s needed.

When choosing which technique to utilize for toggling temporarily hidden content, one should determine the desired toggle micro-interaction.

If a component only requires a simple show and hide interaction, toggle a display: none class or hidden attribute on an element. If a more complex transition is required, like when transitioning an off-screen navigation into the viewport, use visibility: hidden; along with other CSS positioning and transform properties to toggle the state of the component.

To reveal content hidden via these methods, you will need to utilize JavaScript to toggle attributes or classes, or you will need to rely on the CSS :target pseudo selector.

Here is a quick demo of toggling content using CSS visibility and display properties:

See the Pen toggles by Scott (@scottohara) on CodePen.

Hiding Content Visually

Visually hidden techniques allow for content to be hidden from sighted users while still allowing ATs to discover and interact with the content.

There are multiple ways to use CSS to visually hide content, but the following example will cover the majority of use cases, as it can meet the needs of both temporarily hidden content, and purposefully visually hidden content.

/* ie9+ */
.sr-only:not(:focus):not(:active) {
  clip: rect(0 0 0 0);
  clip-path: inset(100%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap; 
  width: 1px;
}

The above “sr-only” class is utilizing various declarations to shrink an element into a 1px square, hiding any overflow, and absolutely positioning the element to remove any ‘physical’ trace of it from the normal document flow.

What the :not portions of the selector are doing is allowing a means for any element, that can receive keyboard focus, to come into view when focused or activated by the user. So elements that normally can’t receive focus, like paragraphs, will not become visible if a user navigates through content via screen reader controls or the tab key, but natively focusable elements, or elements with a tabindex will have these elements appear in the DOM, upon focus.

As for some examples, this class can help provide context to read more links:

<a href="#foo">
  Read more
  <!-- always visually hidden -->
  <span class="sr-only">
    about how to visually hide content
  </span>
</a>

inline skip links:

<!-- temporarily hidden until <a> is focused -->
<a href="#main_content" class="sr-only">
  Jump to main content
</a>

or labels to form components where context is visually implied, but may not be apparent to users reliant on screen readers.

<fieldset class="date-range-component">
  <legend>
    Select Start and End Dates
  </legend>

  <!-- always visually hidden -->
  <label for="start_date" class="sr-only">
    Start Date:
  </label>
  <input type="date" id="start_date" name="start_date">

  <span>
    to
  </span>

  <label for="end_date" class="sr-only">
    End Date:
  </label>
  <input type="date" id="end_date" name="end_date">
</fieldset>

If you’d like to learn more about .sr-only classes, there are additional links, in the resources section, at the end of the article.

Visually Hidden: Off Screen

Another method for hiding content from view is to push it off to the left of the visible viewport of the browser.

/* baseline rules for an off-screen class */
.off-screen {
  left: -100vw;
  position: absolute;
}

Absolutely positioning content off screen removes the content from the document flow, while also retaining access to it for ATs. It’s best to position content off to the left, as positioning it to the right may create horizontal scroll bars, if an overflow-x: hidden; is not set on containing elements.

Where this sort of technique can be preferred over the .sr-only class is when it’s modified for content that will transition from off-screen to within the view port, upon focus.

e.g. grouped skip links

<!--
  a list of skip links to jump directly to the 
  primary navigation or content of an interface.
-->
<ul class="off-screen-ul">
  <li>
    <a href="#primary_nav" class="skip-link">
      Skip to Primary Navigation
    </a>
  </li>
  <li>
    <a href="#primary_content" class="skip-link">
      Skip to Primary Content
    </a>
  </li>
</ul>
/*
  hides the list off screen, since these are links
  that are only useful for keyboard users, and do
  not require being consistently visible.
 */
.off-screen-ul {
  left: -100vw;
  list-style: none;
  position: absolute;
}

/*
  Style the skip links to be fixed to the 
  top of the page, and have an initial 
  negative Y-axis value.
 */
.skip-link {
  background: #000;
  color: #fff;
  left: 0;
  padding: .75em;
  position: fixed;
  top: 0;
  transform: translateY(-10em);
  transition: transform .2s ease-in-out;
}

/*
  Upon focus of the skip link, transition
  it into view by returning it's Y-axis to
  the default 0 value.
 */
.skip-link:focus {
  transform: translateY(0em);
}

Here’s a codepen of the above example.

See the Pen Simple off-screen skip link nav by Scott (@scottohara) on CodePen.

In the above, I’ve expanded the basic .off-screen class to hide the unordered list from being visible. The skip links have a variation of an off-screen class, as I want them to always be positioned at the top of the screen, but not transition in from the left. Since I want these links to individually slide into view, I’m also using the transform property, rather than top property, as transitioning transforms has better performance (youtube video of Will Boyd’s CSS Conf 2016 talk).

Exploiting a Completely Hidden Content Loophole

The previously outlined methods to completely hide content will negate the user’s ability to directly access that content. However, there actually is a way to reveal that content to screen readers.

By attaching an aria-describedby or aria-labelledby attribute to a focusable element, and setting the ARIA attribute’s value to the completely hidden element’s ID, screen readers will announce the content of the completely hidden element.

The use case for doing this would be if there was a need to provide additional context to an element (like form inputs), or to provide a label to an element without having that content announced multiple times.

For example:

<!-- 
  the sr-only class does not completely hide content. 
  These instructions will still be discovered by screen readers.
-->
<p class="sr-only" id="example_desc">
  Here are specific instructions for the type of information this form input is expecting to receive...
</p>

...

<label for="example">
  Example
</label>
<input type="text" id="example" aria-describedby="example_desc">

The above example would allow screen readers to read the contents of the example description as a user parses through the document. The issue with this is that the information may be confusing to users out of context of its application, and moreover, it will be announced twice to the user. Once while parsing through the document, and again when focus is set to the input.

To get around these issues, the instructions can be completely hidden:

<!-- 
  using the hidden attribute, this description is now inaccessible
  via normal methods of content discovery, but the aria-describedby,
  attribute on the input, can still announce it to the user.
-->
<p hidden id="updated_example_desc">
  Here are specific instructions for the type of information this form input is expecting to receive...
</p>

...

<label for="example">
  Example
</label>
<input type="text" id="example_2" aria-describedby="updated_example_desc">

Now the instructions are hidden from everyone in the normal document flow, but the ARIA attribute can still reference the ID, and reveal the content to the screen reader.

Hiding Content from Assistive Technology

As mentioned, sometimes content is for decorative purposes only, and it would be optimal to not announce this content to assistive technology.

For instance, icons and specifically icon fonts.

The Filament Group wrote an excellent article Bulletproof Accessible Icon Fonts that I suggest reading if you or your organization is still using icon fonts. I also suggest reading Making the Switch Away from Icon Fonts to SVG, by Sara Soueidan.

The short version is that icon fonts can be interpreted strangely by screen readers, and as they should be coupled with text labels, or at the very least, visually hidden accessible text to describe the icon’s purpose, the icon is really just for decoration.

With this in mind, we can apply aria-hidden="true" to an element to let screen readers know they should ignore this content.

<button type="button">
  <!-- an X icon -->
  <span class="icon-close" aria-hidden="true"></span> 
  <!-- replace "this component" with appropriate text -->
  <span class="sr-only">Close "this component"</span>
</button>

The above markup utilizes an “X” icon as the visual indicator for the close button, but as it’s an icon font, and doesn’t provide adequate context as a label, the icon is hidden from ATs. Visually hidden text is instead added to properly announce to screen readers that this is a close button for a particular component.

Some things to be aware of if using aria-hidden:

  1. Do not use it on focusable elements. These elements would still be focusable but not properly announced by ATs. You could add a tabindex="-1" on the focusable element to remove it from the tab order, but the problem remains that you’ve now created an element that can’t be accessed by sighted keyboard users. Again, just don’t use aria-hidden on focusable content, and you’ll be all set.

  2. Unlike [hidden] where setting a display: block; will override the attribute’s default styling and semantics, aria-hidden is not tied to any CSS rules, so the only way to reveal its contents to users is to set the value to “false” or to remove the attribute all together.

  3. If you are using aria-hidden as a hook to hide/show content with JavaScript, then do not hard code aria-hidden="true" on the containing element. If JavaScript becomes unavailable, hard-coded aria-hidden content would be permanently inaccessible, as there’d be no available way to change the state of that element.

To wrap this all up…

Let’s do a quick recap:

  1. There are three categories of hidden content
    • Temporarily Hidden
    • Purposefully Visually Hidden
    • Purposefully Hidden from Assistive Technology
  2. Based on the type of content, you will need to use an appropriate technique to hide it, via:
    • Using CSS or [hidden] to hide content completely
    • Using visually-hidden only classes to visually hide content, but keep it available for assistive technologies
    • Or using aria-hidden to hide content specifically from screen readers.

Going back to my initial question “why are we hiding content?”, it’s apparent that there are some elements of a UI that truly need to be hidden. And while we have techniques to hide content, but still make it accessible for assistive technology users, I wouldn’t necessarily jump to these techniques as design solutions.

The goal should be to craft interfaces and experiences that are accessible and understandable to as many people as possible. Not to create interfaces where we can shoe horn in additional context by visually hiding it by default.