Clip Your Hidden Content For Better Accessibility

This post originally appeared on the Yahoo! Accessibility blog.

There's a balance between creating a clean, simple visual design and providing accessible content and functionality. One common solution is to provide text to screen readers that is hidden via CSS.

comparison of form inputs with visible and invisible labelshidden and visible form label

The top search form has a hidden label using the clip pattern. The bottom form's label is without CSS

Please visit our Screen Reader Test: Accessible solutions for hiding content for more information on the accessibility of hidden content. This article demonstrates how screen readers announce the various methods of hiding elements, such as labels, with CSS.

Comments on Jonathan's blog, on Adaptive Themes, and other venues, show that many developers are still confused by what does what and for whom. So let's first recap what basic declarations mean when it comes to hiding content:

Techniques that make content accessible to AT.


<position:absolute;
<clip:rect(1px 1px 1px 1px);

<position:absolute;left:-999em;
<position:absolute;top:-999em;
<text-indent:-999em;
Techniques that make content inaccessible (invisible to all users).
<visibility:hidden; /* in most screen readers*/
<display:none;
/*in most screen readers and with some exceptions
http://juicystudio.com/article/screen-readers-display-none.php*/


overflow:hidden;
height:0; /* In VoiceOver */

As a side note, if content is ignored in Voice Over with the latest rule above, it is not because of height:0 (as it is often suggested), but because of overflow:hidden, which makes sense as a 0x0 pixel box does not hide content.

Techniques and challenges

You can check Hiding Content for Accessibility for a summary of different techniques with their pitfalls; but in short, these are the common issues a technique should address:

It should make the element disappear (as if it was not in the document at all)
This means there should be no unwanted gaps, no scrollbars, no issues related to stacking (clickability), etc.

It should prevent unexpected scrolling when elements within the hidden container get focus
This means the page should not jump when users tab through focusable elements inside the hidden container

It should be bidi-friendly.
This means the technique should support both right-to-left and left-to-right interfaces

Note: an easy way to find out if the content you're hiding is accessible to screen-readers is to use the keyboard to reach any focusable element inside your hidden container (if needed, add a link to the container). If keyboard navigation takes you to an element inside the hidden container, then its content is accessible.

The best method (on paper)

In my humble opinion, the best method - by far - is the clip technique from Jeff Burnz. It is short, simple, and direction agnostic. It looks like this:


.element-invisible {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
}

Note that all browsers understand the first clip declaration, but IE 6 and 7 do not understand the proper syntax in the second one (with comma separated values).

Unfortunately, it appears that Webkit, Opera and to some extent IE do not play ball with this method. They create unwanted scrollbars depending on the layout of the hidden element and/or its position on the page. In short, these browsers clip the box but behave as if the element being clipped maintained its original layout. Note that the box is really clipped though, as even if it seems to take space on the page, it does not interfere with surrounding elements (i.e. links that would be "behind" the box if it was not clipped are clickable).

This behavior seems to go against the spec though, as it says: Content that has been clipped does not cause overflow. If anyone has a definitive answer about this, please chime in and join the discussion.

Plan B

Jonathan Snook and his colleagues at Yahoo! came up with a fix that addresses the Webkit/Opera issue. It combines two techniques:


.element-invisible {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
}

This would be good enough if we could rely on clip, but we can't. Remember that the main difference between clip and overflow is that they do not target the same box. One addresses the border box while the other addresses the content box. This means the above rule would effectively "kill" border and padding (edges outside the content box), but would fail to prevent these from creating scrollbars in most browsers.
So, a "safer" rule could be:


.element-invisible {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding:0 !important;
border:0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}

The above is almost identical to this rule I found in an html5boilerplate ticket. It does not look nice, but it should prevent issues related to all box's edges. As a side note, I'd agree with the author that using only the "old" clip notation (the "IE6, IE7" declaration above) should work just fine.

I wrote the declarations in the previous rule in a particular order because if one day clip works as everyone would expect, then we could drop all declarations after clip, and go back to the original:


.element-invisible {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
}

Tabbing Navigation

This "issue" is about sighted keyboard users. If the hidden content is vertically offset (i.e. position:absolute; top:-999em;) then the page scrolls whenever a focusable element within the hidden container is reached. In any case, even if there is no offset (i.e. position:absolute; left:-999em;), the behavior is confusing as there is no visual cue to tell these users where they are on the page.

We could try to "fix" this for people using both a pointing device and the keyboard to navigate, but it would not be a sure thing. The idea is to rely on the pseudo-class :hover to remove all focusable elements in the hidden container from the tabbing sequence. Using a rule like this for example:


body:hover .element-invisible a,
body:hover .element-invisible input,
body:hover .element-invisible button {
display: none;
}

The above should remove links and form controls from the tabbing sequence as long as the user has his mouse cursor over the page (note that this rule may create performance issue in IE7).

A word about semantics

I'm not sure about using the word "element" within the class name (as in element-hidden). It sounds redundant to me as a selector always matches an element. I prefer the html5boilerplate approach; they use visuallyhidden, but I'd throw a hyphen in there:


.visually-hidden {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding:0 !important;
border:0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
body:hover .visually-hidden a,
body:hover .visually-hidden input,
body:hover .visually-hidden button {
display: none !important;
}

The !important declarations are meant to overwrite the styling of the box (if any).

Making content inaccesible.

If your goal is to hide content from all users, then follow Webaim's advice and use both visibility and display:


.hidden {
visibility: hidden;
display: none;
}

This is from 2007 though, so if anybody knows better, please chime in and join the discussion.

Further readings