About a year ago I wrote “The graceful degradation myth” in which I talked about the entanglement phenomenon that occurs when you mix JavaScript functionality with CSS “initial states”. By that I mean, if you’ve got a block of content that is initially hidden via CSS (i.e. display: none;
) and is only made visible through JavaScript functionality (like an onclick
event), you run the risk of barring access to that hidden content to visitors who don’t have JavaScript running on their browser–if you don’t do it right that is.
Since I wrote that post I came up with a technique that solved the problem (as well as the “flicker” problem that occurs with some fixes). This past week I came up with what I believe is an even better solution which I plan on using in upcoming projects. I’ll share both with you here in this post.
Before I get to the solutions, I’d like to touch briefly on the subject of the three layers of separation. Those being of course content (HTML), presentation (CSS), and behaviour (JavaScript). Most people in the web standards community know of these, but what I seldom hear is the idea that one layer should never break the other. See, you start with HTML on top of which you place CSS and then comes JavaScript (if you’re using all three that is, though the sequence of the last two is debatable). When you go from CSS to JavaScript you should never do something in the CSS that relies on the JavaScript. In other words, dependency should be unidirectional–downward. A layer should only be dependent on one that it’s applied to, not the other way around. Therefore, when implementing a show/hide mechanism in the behavioural layer, you should do so in a way that still allows the first two layers to be accessible should the third not be available.
The reason I just covered the three layers of separation is because a lot of JavaScript implementations break the rule of non obstruction. A lot of implementations will hide content in CSS and only make it available through JavaScript, thus tangling the two layers together and breaking the separation model. The following solutions avoid this phenomenon.
Create a CSS file that will contain all of the “initial state” rules for your behavioural layer. So for example in the case of a flyout menu system, if all sub-menus with the class name “flyout” are to be initially hidden when the page loads your CSS file would contain the following rule:
.flyout{display: none;}
Then, link the CSS file to your page using JavaScript. That way, if JavaScript isn’t available the CSS file never gets loaded and the rule never gets applied. Thus the content remains visible and accessible. The way I used to do this was with the following line of JavaScript code in the <head> of the page:
document.write("<link rel='stylesheet' href='initial_states.css' type='text/css' \/>");
The problem with this technique is that I’m using document.write()
which is bad. It’s bad because it ties the instruction to a specific place in the document, it’s archaic and isn’t supported in pure XHTML implementations (such as this site that’s delivered with the application/xhtml+xml
MIME type).
The better solution builds on the basic concept of the original but does it without document.write()
. This time your initial state CSS rules will look like this:
body.hasJS .flyout{display: none;}
And you can go ahead and load the CSS file in the traditional way without using JavaScript. The key rather is to assign the “hasJS
” class to the body element through JavaScript. If JavaScript doesn’t exist, it doesn’t get set and the rule doesn’t get applied. You can still keep the initial states in a separate file if you’d like but it isn’t necessary. So long as the rule exists somewhere. The simplest implementation of this technique is to have the following line of JavaScript code on the first line inside the body element:
document.body.className += "hasJS";
The reason for this is because doing it in an onload
event will cause a flicker where the page will load, then the content will be hidden. This way, the class is applied before any content is parsed. There are of course better ways of applying the class name (such as an addClassName()
function that only adds the class name if it doesn’t already exist).
I haven’t fully tested the better technique on a wide variety of platforms as I have with the standard one, so if you have any success with it, please let me know.
I’ve mocked up a quick example page so you can see it in action.
–30–
Read more from the archive.