DEV Community

Robert Pearce
Robert Pearce

Posted on

Future-proof two types of visually hidden content

Originally posted on https://dear-dia11y.com/future-proof-two-types-of-visually-hidden-content.html

Dear dia11y,

I would prefer to not hide content from anyone, but there are times when I need to provide extra context and functionality when a design is not inclusive enough.

The .visually-hidden "clip pattern" from The A11y Project is excellent for hiding content that is intended for screen reader users, and it can be leveraged to provide interactive elements, like <button>s, that reveal themselves on focus for screen reader, keyboard, and other assistive device users alike. Here's what that website's recommendation is:

.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;
}
Enter fullscreen mode Exit fullscreen mode

For most use cases, this is great. But my work in large codebases has shown me that if I want this to not take up any space, I need to get bossy and guard against future changes, as well.

.visually-hidden:not(:focus):not(:active) {
  border: none !important;
  clip-path: inset(50%) !important;
  clip: rect(0, 0, 0, 0) !important;
  height: 1px !important;
  margin: 0 !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
}
Enter fullscreen mode Exit fullscreen mode

While most things I do in my life aren't important to anyone but me, getting visually hidden content right is very !important.

With these additions we can ensure that we don't break any existing designs.

However, what if we transition from one page / view to another and need to call element.focus() on a visually hidden element in order to manage focus context? We might have something like this:

<h1 class="visually-hidden" tabindex="-1">
  A necessary hidden h1 for whatever reason
</h1>
Enter fullscreen mode Exit fullscreen mode

If we call .focus() on that <h1>, the :not(:focus) part of the .visually-hidden definition will cause it to be not hidden!

The .visually-hidden class is intended to be a catch-all to also account for links and buttons, and we definitely want to leverage that, but there are times where we know we will never want it to be focused, so we can be explicit about doing so by having an additional class, .visually-hidden-always, and reuse our code via a grouped selector.

.visually-hidden:not(:focus):not(:active),
.visually-hidden-always {
  border: none !important;
  clip-path: inset(50%) !important;
  clip: rect(0, 0, 0, 0) !important;
  height: 1px !important;
  margin: 0 !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
}
Enter fullscreen mode Exit fullscreen mode

Something important to note is the usage of 1px. I may be tempted to use em or rem or ch or ex or something clever, but I should not do this! Depending on the font-size, the browser, the monitor DPI, etc, something like 0.1em can end up as a half pixel and be rounded down to 0, thus making this inaccessible to screen readers! Use 1px, I beg of my future self; it's okay to use px for certain things.

Yours,

Robert W. Pearce

Top comments (0)