Using CSS clip as an Accessible Method of Hiding Content

Its relatively easy to hide content in Drupal using CSS, however its a whole different ball game to hide content and keep it accessible to all site visitors. Disabled web users may be using a screen reader or other Assistive Technology. For Drupal 7 we wanted a way to hide content that worked in all browsers and avoided many of the issues associated with current techniques.

The idea was to develop a new CSS class that could be applied to any element we needed to hide. We called this class .element-invisible and set about testing properties and values to make this work, in all browsers and all AT devices. First lets look at some of the current techniques we looked into and tested for Drupal 7, then I'll introduce a new technique I came up using the little known clip property and see how it stacks up - I think you'll be intrigued.

Update: A clip method was committed for Drupal 7.

Current Accessibility Techniques for Hiding Content

At the moment theres really only two methods that are in common usage - the text-indent method (not unlike the Phark text-image-replacement method) and using absolute positioning. Lets take a look at both of these and analyze their flaws.

Text Indent

This is probably the easiest to understand and implement - the text is given a large negative value to move it off-screen (outside the boundaries of the browser window):

.element-invisible {
  text-indent: -9999em;
  outline: 0;
}

The outline is zeroed out otherwise if the element is focused we will see the outline extend around the element and all the way off the screen.

The big problem with this technique is with RTL (Right to Left languages) - any way we deal with text indent there will be issues in at least one browser - in short, its just a bloody pain in the arse dealing with negative text indent and RTL, so much so it simply rules it out as a candidate.

Position Absolute - Collapse the Element

There are principally two main methods that use position absolute - one simply collaspses the element and the other moves the element off screen.

Lets look at the first one. This is pretty cool technique that sets the height to zero, hides the overflow and takes the entire element out of the flow of the document.

.element-invisible {
  height: 0;
  overflow: hidden;
  position: absolute;
}

This is in fact the original method we choose to put into Drupal 7, it was clean, easy to understand and it worked in all browsers - or so we thought. This method has one major flaw and its the height property value (zero). The problem is with Apples Voice Over software - it interprets elements with height:0; to be invisible and does not read them out. This issue was first reported by Everett Zufelt back in Feb, 2010. Subsequent releases of Voice Over have not changed the situation (as of writing in any case). Both NVDA and Jaws screen-readers had no problems with this implementation so this was a real blow and meant we needed to find a new, better technique.

Position Absolute - Move it Off-Screen

One of the oldest techniques for hiding content accessibly is to move the element out of the view-port. Easy to grasp this technique merely sets a big negative top value to shift the element out of site.

.element-invisible {
  position: absolute;
  top: -999999em;
  left: auto;
  width: 1px;
  height: 1px;
  overflow:hidden;
}

This technique is pretty solid and works in all browsers and won't give any hassles in RTL websites, however there are two main issues with this technique - its relatively easy to break, and it can cause a page "jump" if applied to focusable elements, which can be very confusing to sighted keyboard users.

Let say you apply this to an H2 heading, and then start adding other generic styles to all H2 headings - you can and probably will pretty quickly start having issues with this. The only way around this is to start adding many other properties and setting them to !important, so pretty quickly your nice simple technique is bloated and is going to end up looking something like this:

.element-invisible {
  background: transparent none !important;
  border: none !important;
  display: block !important;
  height: 1px !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  top: -999999em !important;
  width: 1px !important;
}

This is not so bad, but certainly rather crufty, but at a pinch it would get the job done - however we still have the big problem of the page "jump" issue if this is applied to a focusable element, such as a link, like skip navigation links. WebAim and a few others endorse using the LEFT property instead of TOP, but this no go for Drupal because of major pain-in-the-butt issues with RTL.

The Clip Method for Accessibly Hiding Content

In early May 2010 I was getting pretty frustrated with this issue so I pulled out a big HTML reference and started scanning through it for any, and I mean ANY property I might have overlooked that could possible be used to solve this thorny issue. It was then I recalled using clip on a recent project so I looked up its values and yes, it can have 0 as a value. My first iteration was this:

.element-invisible {
  position: absolute;
  clip: rect(0px 0px 0px 0px);
}

At first I only tested this Firefox, IE6/7 and NVDA and it seemed to be working well. Problem was I used the depreciated syntax (no comma delimiters) and IE8 uses the recommended syntax while IE6 and IE7 will only work with space delimiters.

I left it for a while since there didn't seem to be much support for the concept and it is a pretty radical idea, however more recently support has grown and a better, cross browser implementation was developed by casey:

.element-invisible {
  position: absolute !important;
  clip: rect(0, 0, 0, 0);
}
/* IE7 \*/
\*:first-child + html .element-invisible {
  clip: rect(0 0 0 0);
}
/* IE6 \*/
\* html .element-invisible {
  clip: rect(0 0 0 0);
}

The strike against this are the browser hacks - since we cant load conditional stylesheets for Drupal core we have to do something like this in the system-behaviors.css file directly. Additionally this still does not solve the page jump problem, which I think can only be solved by using a position absolute LEFT/RIGHT method, however this could be very difficult to get working reliably and we just have to live with the fact that hiding focusable elements (pretty much the only one we would hide is the skip navigation link) comes down to a particular sites requirement.

The main reason I like this method is that is its 1) hard to break and 2) relatively lightweight CSS. I can live with the hacks - we have been for many years in Garland already.

I've now tested this in Firefox, Opera 10, Chrome, IE6/7/8, Safari 4 and 5 (but not with Voice Over) and NVDA - and it works. Additionally its very hard to break, in fact I haven't been able to break it yet other than re-declaring clip, which would be very rare indeed.

Unfortunately I do not have a Mac and cannot test Voice Over, nor do I have Jaws, so I'm out there also.

If you do have a Mac, or Jaws, please join the effort - you can read the entire issue here: http://drupal.org/node/718922

I have big hopes for the clip technique, if we can test it thoroughly and it works, we would have pioneered a brand new accessibility technique and one that could be most useful to many other websites and developers, not just for Drupal, but for disabled web users everywhere.

Update 23rd June 2010

Theres an off-left method in the thread linked to that uses left:auto; for RTL that seems to work well in early testing, this good news for those wanting to use this technique in Drupal.

Also we have tested the clip method in all major browsers and screen-readers (the newer method - see below) and found its working - both hiding content from normal visual display and being announced by screen-readers (Jaws, NVDA and Apple Voice Over).

The new method uses 1px value instead of 0:

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

So what have we got to hide here...?

One of the commenters below (#5) asked about what it is we're trying to hide. In Drupal 7 we added proper H2 headings to most lists - such as menu link lists (main menu/secondary menu - this is done in theme_links) search block gets a heading if one is not set in the block settings, breadcrumbs have a heading also. Many "active" links get a hidden message and we envision adding other elements to help screen-reader users so having a reliable way to hide these elements from visual display is imperative (since sighted users don't need these)

I made a couple of screenshots that show some of these hidden elements - this is a basic forum post in Garland for Drupal 7. Pay attention to the main menu, breadcrumbs and the search block.

Drupal 7 Garland theme - with CSS
Drupal 7 Garland theme - no CSS
Submitted by Jeff Burnz on

Comments

peach - sooperthemes's picture

RTL

Hi, I don't really have experience with RTL theming, could you explain how left:-999999em; has problems working with RTL?

Jeff Burnz's picture

...

The problem is browser inconsistency - at some point you will get a horizontal scroll-bar - it'd be pretty long winded for me to explain here the exact issues with each browser, but in short its beyond an IE vs everything else, browsers just react different to different property/value combination's (in RTL/LTR) and I did not find anything that came even close to being acceptable - I sort of summarized things here http://drupal.org/node/718922#comment-2919182

sun's picture

Automatic invalid rule fallback?

Nice summary and promotion.

I'd like to know whether this (or vice-versa) would work:

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

Jonathan Neal's picture

focus + active support

Great post. Let me share a small ammendment for focus and active support. This will work in IE6+ as well as Firefox, Safari, Chrome, and Opera.

.obscure {
clip: rect(0 0 0 0);
position: absolute;
}

.obscure:active,
.obscure:focus {
clip: auto;
position: static;
}

The :active is required for IE6 and IE7. This is a fast, simple solution when you need to obscure a "Skip to main content link" or an HTML5 heading. This is especially great because you can obscure any text on the page, but allow focusable text to appear when it's appropriate (i.e. on focus).

me's picture

The linked D.O node sheds a

The linked D.O node sheds a little light on what you're attempting to do here, but I'm still in the dark about what types of content you're attempting to hide from sighted users, while keeping them visible to xcreen readers?

Jeff Burnz's picture

...

I added a couple of screen-shots to show what I'm on about - best I can suggest is to download and play around with Drupal 7 - look at the source code or switch off CSS, you'll see hidden headings and other messages (such as active trail messages on local tasks). These were all added in D7 for accessibility - we want Drupal 7 to be the most accessible CMS ever built, and we're well on the way to that.

Jonathan Neal's picture

Awesome

That's awesome to hear, Jeff! I like how alpha 5 validates out of the box too. Why are you going with XHTML + RDFa over HTML5 + ARIA, and Microdata or Microformatting?

Also, in alpha 5 you may either want to adjust the way the branding is added, or adjust the alt content, otherwise there's a potential quirk with screenreaders and search engines that will read the document as "Site Title Site Title". Does that make sense? I hope it helps. Great work!

Jeff Burnz's picture

...

Well I think you raise a very pertinent question here and not one I am qualified to answer at the moment, my head has been so embedded in other areas for a long time I haven't really had time to catch up with these subjects in any depth. I don't think there's heavy use of microformats in Drupal 7 - but I would surely like to look at microformats a lot more for Drupal 8.

Re the logo/site name issue - I noticed this quirk the other day when I was testing Jaws 11, its very annoying to have it read out twice so yes, I really do need to rethink the whole logo/site name setup.

Jonathan Neal's picture

Logo / Site Name issue

The solution should be rather simple. When a site graphic IS NOT present, the header contains the Site Title text. When the site graphic IS present, the header contains an element with the alternate (the alt attribute) content set as the Site Title text.

Example when graphic is present: <h1><img alt="Site Title" src="site-graphic" /></h1>

Example when graphic is not present: <h1>Site Title</h1> or <h1><span>Site Title</span></h1>

Most search engines, accessibility tools, and document outliners will properly interpret this alt content. For HTML5 sites you can experiment with gsnedder's outliner @ http://gsnedders.html5.org/outliner/

Also, you should pop on freenode IRC sometime. We geek out about this stuff all the time.

edit: code tags added by admin.

ikyat's picture

great!

:)

Jesse Beach's picture

heading tags

Jeff, I took of a little comment in the post: "In Drupal 7 we added proper H2 headings to most lists..."

What do you think about the tension between organized layout and SEO? In my opinion, h2 should NEVER be used to denote an aside component heading. The headings are often too generic to lend any semantic value to the page. Better to save h2 for the main content node that an editor or author could use.

For instance, this h2 heading in your post is clear and meaningful. It adds great value to the page:
"Current Accessibility Techniques for Hiding Content"

But it's competing with this h2 in the sidebar that adds no semantic value to the page:
"Recent Posts"

I would wrap "Recent Posts" in h3 or better, h4. This overuse of h2 is a big ding for Drupal in terms of out-of-the-box SEO optimization. What do you think?

Jeff Burnz's picture

...

Using heading tags for some sort of SEO effort is not my intention, we use them because they offer a powerful way for screen reader users to navigate the page. I think the heading you use is really dependent on the structure of each page. In our case semantic structure is more important to us - using H3 for a sidebar block would be wrong because it would be out of order in the heading level structure - proper heading level navigation requires properly nested headings.

amxl's picture

the logo/site name issue - I

the logo/site name issue - I noticed this quirk the other day when I was testing Jaws 11, its very annoying to have it read out twice so yes, I really do need to rethink the whole logo/site name setup.

debbiefisher's picture

great post

great post

financial aid for college's picture

amazing post

found your site on del.icio.us today and really liked it.. i bookmarked it and will be back to check it out some more later

Rich's picture

I'm missing something

OK, so there must be a really obvious reason you're not suggesting the use of {display:none;} but I have no idea what it is.

Jeff Burnz's picture

Good question

Its because display:none; hides the content from all user agents - regular browsers and screen-readers, which is good if that's what you need.

A good example where display:none; is used correctly in Drupal are the collapsible fieldsets which use jQuery's .hide() function. When a screen-reader encounters a collapsed fieldset it will announce the anchor then skip the hidden content - which is good and works the same as it does for those using a regular browser.

But, what if you need a way to hide content from regular browsers, but leave it available to screen-readers - for example you might have a hidden message that announces important information to a screen-reader that is otherwise only availble visually - in Drupal 7 we have done this a lot, hiding messages in the output that act as way-points to screen-reader users.

This is actually much harder than it sounds because browsers and screen-readers have varying levels of support for CSS and treat property values slightly differently. Furthermore it can't be easy to break - meaning that other CSS that gets added for design purposes might be inherited by the element we are trying to hide - and suddenly appear, or start taking up space (e.g. inherit some padding or margin etc).

I can tell you this was not an easy thing to do in Drupal (took about a year to develop a robust solution) and the clip method turned out to be one of the better methods we came up with because its very very hard to break and is a simple clean solution (3 lines of CSS).

Rich's picture

Thanks

Have been using the absolute positioning up to now but wondered why display:none wasn't an accessible option.
I'm keen to make my sites as accessible as possible and must confess I thought screen readers just ignored css (being that it's for presentation and not semantic), so, glad to be better informed so I can improve things going forward.
Thanks for answering.

Jeff Burnz's picture

You’re welcome and its a

You're welcome and its a common misconception that screen-readers don't read CSS - they most certainly do, and it can get even more complicated because it can often depend on which browser you are using, for example IE8 + Jaws 10, or Firefox + Jaws 11, or NVDA with Opera, or Voice Over with Safari or Firefox - these various combination's can produce subtle differences that end up being big deals in certain situations - which I would never expect your average web designer or site builder to have to figure out, its just too much testing, so as long as you're not doing anything really silly you're way ahead of the pack...

Some extra reading: http://drupal.org/node/464472

Arkaed's picture

Problem with hiding a label that has width set in css

This is a great post but I'm having a problem with Chrome and Opera using this technique.

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

I'm trying to hide a label element for a search field.

Search

The page has a container that has width:92% with auto margins. The search field is located in the top right corner of the container. I also have width 20em set for label elements in the stylesheet. Now what happens here with Chrome and Opera is that the supposedly hidden label still has the width and although Chrome and Opeara don't actually show the label they still reserve the space for the label and the width of the label goes over the container and that causes a horizonlal scroll bar to appear. The problem goes away when I set the width to 0px for the hideFromScreen class. I understood that I woulnd't have to do this. Or is there something that I'm missing here?