Who’s Got Style?

By Nicholas C. ZakasJune 7th, 2007

With DOM support across all A-grade browsers, many basic (and some complex) interactions can be accomplished with relative ease. Things like adding and removing elements, inserting HTML text, and working with events are now reasonably manageable on a cross-browser basis. There are, of course, some quirks that have to be accounted for, but generally speaking, most things work as you would expect them to. Except for dynamically inserting CSS into your page.

When writing HTML, CSS can be embedded in the page using the <style> element, like this:

<style type="text/css">
  a {     color: red;
  }
</style>

This block of code can be placed anywhere in a page and its rules will be applied to the entire page. Since we have the DOM API, which gives us the ability to dynamically create elements, attributes, and text nodes with a document, one would assume that some very basic JavaScript code could be used to mimic this HTML code. Logically, it would work like this:

var styleElement = document.createElement("style");
styleElement.type = "text/css";
styleElement.appendChild(document.createTextNode("a { color: red; }"));
document.body.appendChild(styleElement);

It should be just that easy…but it’s not. For Opera and Firefox, browsers that purport to have great standards support, this works perfectly well. In Safari and Internet Explorer it fails, though not for the same reason.

Safari requires dynamically created <style> elements to be inserted into the <head> for the rules to be applied, which is a fairly easy change to the previous code:

var styleElement = document.createElement("style");
styleElement.type = "text/css";
styleElement.appendChild(document.createTextNode("a { color: red; }"));
document.getElementsByTagName("head")[0].appendChild(styleElement);

This code now works in Opera, Firefox, and Safari. But what about IE? When IE encounters style.appendChild() it throws the rather obtuse and not-very-helpful error message, “unexpected call to method or property access”. Try replacing that with a call to set innerHTML, and you’ll get an equally useless error message of “unknown runtime error”. What’s going on here?

It turns out that IE won’t let you manipulate <style> elements in this way. There is, however, a different way to do the same thing. IE supports a styleSheet property on each style element that allows for the manipulation of the style sheet and the rules contained within. The styleSheet property has a property called cssText, which can be used to set and retrieve the CSS text for the style sheet. So, the code can be modified to work in IE by doing this:

var styleElement = document.createElement("style");
styleElement.type = "text/css";
if (styleElement.styleSheet) {
  styleElement.styleSheet.cssText = "a { color: red }";
} else {
  styleElement.appendChild(document.createTextNode("a { color: red; }"));
}
document.getElementsByTagName("head")[0].appendChild(styleElement);

This code now works in all A-grade browsers and can be genericized into a function like this:

function addCss(cssCode) {
var styleElement = document.createElement("style");
  styleElement.type = "text/css";
  if (styleElement.styleSheet) {
    styleElement.styleSheet.cssText = cssCode;
  } else {
    styleElement.appendChild(document.createTextNode(cssCode));
  }
  document.getElementsByTagName("head")[0].appendChild(styleElement);
}

Using this function, you can add as many new blocks of CSS code to your page as you would like.

A warning: IE only allows writing to styleSheet.cssText one time per <style> element. If you try to do it more than one time, it can crash the browser. For this reason, it’s best not to reuse <style> elements on your page. Instead, remove them or just add new ones.

26 Comments

  1. I just use YAHOO.util.DOM.setStyle. That seems to work for all browsers.

    Yep, snide remark. But thanks for the wonderful work on YUI and this blog!

  2. YASIEQTMS (yet another stupid IE quirk – thanks Microsoft!)

  3. This is neat and all, but why would you need to add CSS styles dynamically with JS? Wouldn’t putting all your CSS in a stylesheet be the best way to do it?

    Is there an edge case where this is appropriate?

  4. A case I can come up with is something I’m actually in the middle of doing. Similar to how the yahoo mail loading page works.

    I render an initial page and want to then ajax in the css and chunks (Rails partials) to the page while my loading div hovers above.

    I haven’t really seen or thought of a more elegant way to do it, so…if anyone has any thoughts bradswinfrey at (good ol’) yahoo dot com

  5. Victor Morales said:
    June 7, 2007 at 1:45 pm

    Do you have any use case when this would be an appropriate, maintainable solution?

    In my mind using this technique conflicts with the precepts that you discussed in the “Mantainable Javascript” presentation (Modifying CSS from Javascript and viceversa)

  6. I remember having to figure all this stuff out when developing the Y! Site Solution; and spending hours and hours getting this to work just right. I dug into way too many API’s I was not expecting to get involved in. In the end, there was some crazy branching going on.

  7. @Victor – This technique is actually very useful for maintainability purposes. It allows you to dynamically load CSS code from the server and then inject it into your page. The new My Yahoo! uses this technique for accomplishing some pretty cool effects.

    Why not use a dynamically created link tag? That’s another possibility depending on your needs. Using XHR to load CSS versus using a new link tag gives you the ability to a) handle errors and b) determine when the file has been fully downloaded.

  8. @Nicholas: As always, you are one step forward. Few days ago, I was thinking about the dispatcher plugin (YAHOO.util.Dispatcher) and how to introduce support for CSS (inline and remote) as an essential element for the creation of a dynamic area inside the web page, thanks for the research and share this stuff.

    @Ryan and BradW: If you’re creating a dynamic area (DIV), and you need to load a remote content within this area using the connection manager, and you don’t know what kind of widget will be deployed in this area, will be very useful that the chunk (webpart that will be displayed inside the area) be JAVASCRIPT/CSS independent (the chunk will contain the required scripts and the required css), and you can use a dispatcher to:
    1. loading the chunk
    2. processing the response
    3. execute the javascripts tags (inlines and remotes)
    4. including the css tags (inlines and remotes)
    5. displaying the content

    The step 4 will be solved with the Nicholas’ code.

    Actually you can see an example here:
    http://bubbling.comarq.com/themes/bubbling/jscripts/yui-cms/examples/dispatcher/plugin-dispatcher-dynamic-tabs-with-datatable-inside.html
    The “datatable control” tab, will load and display a datatable (including datasource-beta.js and datatable-beta-min.js onDemand), as you can see, the tab’s content are carry on with all the necessary resources, but the datatable’s CSS is defined in the original page. Now, with the Nichalas’s code, the chunk will have a complete independence.

    @Victor: I don’t know why you see a conflict on this technique? If you need to load a remote CSS onDemand, the CSS’s code is not mixed with the javascript, in this case, the javascript’s code is the loader.

  9. Thanks for the information regarding IE, but I think the would always be in the not outside of it. The Safari implementation is correct.

    Even if you leave Safari out, I think putting the outside of the will not work in strict mode.

  10. [...] Who’s Got Style? The Yahoo Blog explains how you can inject style tags into your document using javascript and the DOM. (tags: javascript dom html reference css) [...]

  11. I use dynamic css as described here.

  12. In my last post, I munged up my response… Here’s the complete response.

    Thanks for the information regarding IE, but I think the style tag would always be in the head tag not outside of it. The Safari implementation is correct.

    Even if you leave Safari out, I think putting the style tag outside of the head will not work in strict mode.

    The YUI blog is informative and great.

  13. Interesting article, but I have a small nit to pick.

    You say: “This block of code [style block] can be placed anywhere in a page …”

    According to the HTML 4.01 spec section 14.2.3, style tags must be contained in the head element: “HTML permits any number of STYLE elements in the HEAD section of a document.”

    Link to the spec:
    http://www.w3.org/TR/html401/present/styles.html#edef-STYLE

    I’m not sure what the browser support is for style blocks defined outside the head (most likely it works fine), but there’s no reason not to stick to the spec — append the style block to the head, as you do in the Safari example.

    Also, a comment:
    I don’t really see the maintainability benefit of this technique. If you compress/minify your CSS and allow the browser to download and apply it, what would the benefit of dynamically creating styles be?

    I don’t mean to be negative, the article is well-written and makes some interesting points.

  14. To those would pointed out that the HTML spec says all style elements should be in the head of the document, you are correct. The point I was trying to make is that when a browser makes an exception in one direction it seems odd that it doesn’t make an exception in the other. Why would style tags included inside of the body work when dynamically created style tags placed inside of the body do not? This is what makes our profession so interesting (fun?).

    @Ross – The maintainability aspect is from the separation of JavaScript and CSS, as described in my talk. Victor pointed out that using this function would require embedding of CSS into your JavaScript code. However, that doesn’t have to be case. You can retrieve the CSS via XHR and insert it into the page using this function. That way, the CSS and JavaScript are separated and you are able to dynamically load styles (which becomes necessary more frequently these days) with all the bells and whistles of other XHR requests.

  15. Nicholas,

    Thanks for the clarification. I agree. I think this is why we should use toolkits and libraries.

    A similar problem exists with dynamic script tags — Dynamic Script Tags

    Oliver

  16. [...] Who’s Got Style? Yahoo! User Interface Blog – I ran into this when I started building widgets. [...]

  17. [...] el Yahoo! User Interface Blog, hace unos días, publicaron un interesante artículo sobre como, con DOM, [...]

  18. Regarding why you might want to add style blocks using JavaScript: I’ve worked on a web site builder that allowed users to dynamically change the look of links on the page (and other things), so we needed to be able to throw a style block on the page containing:

    a { color: #F82156; text-decoration: none }
    a:visited { color: #752007; }
    a:hover { text-decoration: underline }

    For this and similar situations, adding script blocks is a great solution.

  19. [...] CSS dinámicamente en tu web En el Yahoo! User Interface Blog, hace unos días, publicaron un interesante artículo sobre como, con DOM, añadir CSS [...]

  20. [...] Who’s Got Style? Posted in перевод, wtf, javascript, программирование | Trackback | [...]

  21. Shlomi Rosenzweig said:
    June 2, 2008 at 9:37 am

    You can also add style sheets using a similar function:

    function addCssFile(css) {
    var styleElement = document.createElement(“link”);
    styleElement.type = “text/css”;
    styleElement.rel=”stylesheet”;
    styleElement.href = css;
    document.getElementsByTagName(“head”)[0].appendChild(styleElement);
    }

  22. So here’s another thing to consider: the above discussion centers on dynamically adding CSS rules to a STYLE tag…what happens if you hit an “@import” rule in the CSS?

  23. Outstanding! Been struggling with this IE “feature” for hours – now works perfectly. Thanks

  24. “var styleElement = document.createElement”
    “A warning: IE only allows writing to styleSheet.cssText one time per element.”

    Does it not slow down the browser that new style elements consisting of only one style element are being made? Is it like a whole new stylesheet for each little bit of css code?
    -thanks

  25. Thanks for the solution and clear explanation, it works perfect for me!