Understanding and solving the JavaScript/CSS entanglement phenomenon
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.
How to layer
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.
The first solution
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
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.
Sphere: Related Content32 Comments
Sorry, the comment form is closed at this time.



February 15th, 2007 at 8:44 am
Interesting technique. Is document.body.classname available before the body element appears on the page? Also is there any conflict with an existing class name on the body element?
February 15th, 2007 at 12:09 pm
Patrick: document.body is only available once it’s been created in the DOM. Putting the code into the <head> will cause an error because the body tag hasn’t been encountered yet by the parser. Putting it immediately after the <body> open tag will ensure that the node’s been created in the DOM. I’ve tested it on: IE6, IE7, Firefox 1.5.0.9 (PC), Firefox 2.0.0.1 (PC/Mac), Safari 2.0.4 (Mac), and it works fine. The only thing I worry about (and would appreciate feedback from people on other platform/browser combinations) is cases where the DOM element isn’t created right away.
As for the conflict with existing class names, that’s what the += is for. You could add a space to the += ” hasJS” value to ensure the proper adding of the class name if you know that one is already there. You could also use a function that handles all the nitty gritty of properly adding the class name without destroying anything.
February 15th, 2007 at 4:29 pm
Sounds good - I’m still a little nervous about assuming the body element has been created, but I could always put some polling code in front of it so it checks to see if the BODY element has been created.
February 15th, 2007 at 6:30 pm
I like it - particularly the potential speed gains resulting from not having the extra HTTP request. Good idea - thanks for sharing.
February 15th, 2007 at 6:39 pm
This is an interesting solution to the hiding by JS problem. I hadn’t thought of it before.
But there is another solution that could be used instead of (or in tandem with) this method. Plus, it allows you to keep all your scripts in the HEAD where they belong.
The trick is to utilize the DOM ready event. While the window.onLoad event must first wait for all elements on a page to load, the DOM ready event runs when just the DOM is ready to go, which is before the page is fully loaded. Think of it as when the browser loads all the tags (and their IDs and classes) but has not yet displayed them (that’s how I think of it anyway…technically it may be altogether different).
So, using DOM ready, you can have a function search the DOM for elements with the .flyout class and hide them (be it through inline style or class) before the browser starts to render them. Or, using your method, simply add a class to the BODY.
This is how almost everything is initialized when using the jQuery (jquery.com) javascript library (highly recommended). The code that would use your method would look something like this:
$(document).ready(function(){
$(”body”).addClass(”hasJS”);
});
Or, a different way would be to do something like:
$(document).ready(function(){
$(”div.flyout”).hide();
});
Either way, they will be hidden before the page starts to render onscreen. Here is a link to a page that describes what is going on much better than I am. http://15daysofjquery.com/quicker/4/
February 15th, 2007 at 6:55 pm
Erik: It would really be great if there was a native .domReady(); method but what you’re referring to is part of jquery and apparently it’s not so reliable (unfortunately). A friend of mine looked into the jquery code and found that it uses a mix of different methods depending on the browser and defaults to .onload() when it can’t rely on any native method. In Safari it runs a .setInterval().
At any rate, I wasn’t looking to compete with jquery’s .ready() so much as deal with the JS/CSS entanglement issue. So really, if you’re happy with .ready() then by all means, use it! :-)
February 15th, 2007 at 7:25 pm
I definitely agree their implementation is a little hacky, and that the browsers really need to conform (but, if they all did, there wouldn’t be much discussion here anyway ;-) ). This is AN alternative, not THE alternative.
But, like you said, the greater point is what matters. Behavior ends with javascript, it should start with javascript too.
February 16th, 2007 at 12:23 am
Have you considered adding the hasJS class to the html element? The selector
.hasJS .whateverwould still be in charge but you could do that in the head and don’t have to clutter the body with script tags for better separation of content and behaviour.February 16th, 2007 at 4:23 am
The solution I developed was to insert a link to the stylesheet into the dom with javascript. That way you can create a separate stylesheet for the features that require javascript without having to preface every selector with html.hasJS. If you don’t mind a little shameless self promotion, I wrote a brief blog post about it here.
February 16th, 2007 at 11:52 am
<html>element hasn’t got class attribute in any of the most popular DTD’s used for http://WWW.E. g.
http://www.w3.org/TR/REC-html40/struct/global.html#h-7.3
I noticed that only when I was trying some time ago to add class=”someLanguage” in addition to lang=”someLanguage” and xml:lang=”someLanguage” attributes, because of lack of :lang() CSS selector support in browsers.
I understand you could insert any custom attribute or element with DOM methods (even
<html hasJS="hasJS">; then CSS like html[hasJS]…), but you should thank browsers developers that “.someClassName” selector works with invalid HTML, thank that HTML isn’t instantly being validated and browsers don’t refuse to display invalid documents.Such a layer-separation-aware article should take it into consideration.
February 16th, 2007 at 12:05 pm
Cezary: Good point. I’m leaning more towards Jim Ramsey’s solution anyway because it allows me to keep the initial states rules in a separate file (as in the first example) and it also keeps me from having to prefix everything with a .hasJS class name.
February 16th, 2007 at 12:06 pm
Good catch, Cezary! I assumed I could attach a class because I can use
htmlas selector…February 17th, 2007 at 12:38 am
It is nice idea, I used to create extra css for js by DOM, this seems much more simple. But in your example there is one big logic mistake. If browser do’nt know document.getElementById it loose sence, because you have think also about this. So in your example should be
if(document.getElementById){document.body.className += “hasJS”;}
February 17th, 2007 at 10:23 pm
Another method would be to make use of the noscript tag, and place a link to a css file in it which would declare the hidden objects to be visible. Doing so could prevent the need for any javascript to run that would change the style on load.
February 18th, 2007 at 1:42 am
noscript seems like it should work, but the noscript tag doesn’t validate when used in the head and the link tag doesn’t validate when used in the body.
February 18th, 2007 at 2:53 pm
Well, to be fair, not all browsers follow w3c spec. :) If you must have validation, the css required to show aforementioned elements would probably only consist of a few lines, so you could use a noscript tag in the body of your html, containing a style tag with the css required to show the hidden elements. Not the cleanest method perhaps, but I still believe this is better than adding more javascript to the page.
February 18th, 2007 at 4:44 pm
Before everyone gets carried away, I’d just to like to point out that when I tried to use the example page via keyboard, I was completely unable to access the link by tabbing in either Firefox 2 or IE6. Personally, I’d be very uncomfortable with any potential solution that created barriers for keyboard navigators.
February 18th, 2007 at 10:48 pm
Mel: The example wasn’t written with the intent of demonstrating the accessibility of the JavaScript code (which didn’t incorporate a hyperlink at all), rather it was written “quick and dirty” to demonstrate how the page’s initial state would be if JavaScript was available or unavailable in a given browser. However, you’ll be happy to find that I’ve fixed the problem so as to be able to access the content via tabbing. Sorry about the oversight.
February 19th, 2007 at 7:04 am
I like it! Very nice and quite simple!
February 19th, 2007 at 9:45 am
I have discovered one “best practice” using a technique like this. In some cases, especially during development, errors can occur in the code for hiding/revealing parts of the page. For example, your onclick event might not get set up correctly. In such a case, the hidden content will never be revealed and the page will be unusable.
So if possible, after all my setup has run, I run a separate script that removes the CSS I used to temporarily hide the content. That way if my code is borked I still get a usable page.
February 19th, 2007 at 5:49 pm
I believe Shaun Inman was using a similar approach back in 2005, even adding a check for a loaded style sheet along with .hasJS.
February 20th, 2007 at 5:07 pm
[...] Understanding and solving the JavaScript/CSS entanglement phenomenon [...]
February 20th, 2007 at 6:03 pm
I have been applying this technique to the html element for about two years now, without any drawbacks that I can see. Cezary Okupski rightfully points out that the class attribute is not a valid attribute for the html element in any of the appropriate DTDs, but it is important to note two things about that:
1. DTDs are created to validate the document as it is delivered, and not the interpreted or modified document held in memory by the client app. Thus, class attributes added to the html tag are invalid, but className values added to the html element via js are perfectly valid.
2. CSS selectors do not check themselves against the html DTD, and any element can have any class, so long as both the class and element name follow the proper naming convention (e.g., not starting with a number, no spaces, etc.).
So, it is valid to add className values to the html element, and to target your CSS selectors to it.
The pattern that I use is this:
a) when the JS executes, add one permanent and one temporary className:
document.documentElement.className += ‘ domCapable domLoading’;
b) after the DOM has been loaded (via a similar technique to the JQuery “ready”), replace the temp className with one that recognizes the new state:
document.documentElement.className += ‘ domReady’;
document.documentElement.className = document.documentElement.className.replace(/\bdomLoading\b/,”).replace(/\s+/,’ ‘);
Most of my “hide until JS runs” CSS then goes like this, where the “off” state is triggered by the html class and the “on” state is both the default and triggered by a class interactively added by JS:
html.domCapable #foo ul { display:none; }
#foo ul,
div#foo ul.show { display:block; }
(Notice the extra specificity of the added “div” in the “on” state to match the “off” state specificity — you could also just remove the “html” from the “off” state selector.)
February 22nd, 2007 at 8:12 am
Thanks Ara for opening this topic, brilliant.
I’ve been struggling to prevent accessible hidden content from flashing up on page load.
Tried the domready technique but wasn’t impressed with the results.
Even tried placing hidden content in tags then redefining via the DOM. Unfortunately Firefox has a bug.
I prefer Ben’s method purely for removing script from the body but I cannot get it to work with IE.
Any workarounds Ben or just use Ara’s?
February 22nd, 2007 at 10:08 am
Nice work dudes.I
s there a specific reason that these techniques work online but not locally?
February 22nd, 2007 at 10:18 am
mike 2k:)2: I’m not sure what you mean by the technique not working locally. Is it because you’re using IE and it blocks JavaScript on locally loaded files by default?
February 22nd, 2007 at 10:39 am
Yes, but normally once the security risk is accepted the JS runs/ works.
Though my PC has been playing up today.
Works perfectly when online.
February 22nd, 2007 at 1:48 pm
@mike 2k:)2:
Some browsers do not automatically have the document.documentElement object. I’ve corrected this in my library with this line of code:
if (!document.documentElement) document.documentElement = document.getElementsByTagName(’html’)[0];
Sorry I didn’t include that — I thought it was for an ancient browser and not something still in use like IE. Guess I gotta comment my code…
I can’t help you with the local/online issue, but if you’re doing serious development you might want to explore running a webserver on your local machine. You can find easy installers for “WAMP” server configs just through a google search.
February 22nd, 2007 at 2:02 pm
For ofline testing you can use mark of the web http://msdn.microsoft.com/workshop/author/dhtml/overview/motw.asp
February 25th, 2007 at 6:40 pm
Nice idea.
Even so, I’ll think I’ll stick to the method I’ve been using as I:
a) don’t agree with having javascript in the html document (and most definitely not within the body) - this should be in a seperate file.
b) find it useful (and far less confusing) to work with two seperate css files: one which contains the “normal” state of a page, one which adds/overwrites the normal state for those users which have js available.
The technique I use is a variation on the first method. It *does* use document.write, but from within the js file.
I don’t mind it being “tied to a specific place in the document” as that place is and should always be the same anyway: the js file should load from the head after the css file has been loaded.
For those interested: I’ve described this method in more detail as part of an accessible fold-out menu tutorial here:
http://adviesenzo.nl/examples/cssjsmenu/
March 4th, 2007 at 11:33 am
[...] Understanding and solving the HTML/JavaScript/CSS entanglement phenomenon, one layer should never br… dependency should be unidirectional–downward, 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 ph (tags: HTML JavaScript CSS WebDesign) [...]
May 27th, 2008 at 2:55 pm
[...] Understanding and Solving the JavaScript [...]