Add Rules to Stylesheets with JavaScript
Update:Rewrites made to accurately represent current practice for stylesheet injection.
Since we're using so much JavaScript in our web applications these days, we're looking for more ways to keep them fast. We use event delegation to keep event listening efficient, we use function debouncing to limit the number a times a given method can be used, use JavaScript loaders to load only the resources we need, and so on. Another way we can make our pages efficient and fast is to dynamically add and remove styles directly to a stylesheet instead of constantly querying the DOM for elements and applying styles. Here's how it works!
Getting the Stylesheet
Which stylesheet you add the rules to is up to you. If you have a specific stylesheet in mind, you can add an ID
to the LINK
or STYLE
element within your page HTML and get the CSSStyleSheet
object by referencing the element's sheet
property. The stylesheets can be found in the document.styleSheets
object:
var sheets = document.styleSheets; // returns an Array-like StyleSheetList /* Returns: StyleSheetList {0: CSSStyleSheet, 1: CSSStyleSheet, 2: CSSStyleSheet, 3: CSSStyleSheet, 4: CSSStyleSheet, 5: CSSStyleSheet, 6: CSSStyleSheet, 7: CSSStyleSheet, 8: CSSStyleSheet, 9: CSSStyleSheet, 10: CSSStyleSheet, 11: CSSStyleSheet, 12: CSSStyleSheet, 13: CSSStyleSheet, 14: CSSStyleSheet, 15: CSSStyleSheet, length: 16, item: function} */ // Grab the first sheet, regardless of media var sheet = document.styleSheets[0];
One important consideration is the media
of the stylesheet -- you want to ensure you aren't adding rules to a print stylesheet when you expect the styles to display on screen. A CSSStyleSheet
object does have informational properties for you to peruse:
// Get info about the first stylesheet console.log(document.styleSheets[0]); /* Returns: CSSStyleSheet cssRules: CSSRuleList disabled: false href: "https://davidwalsh.name/somesheet.css" media: MediaList ownerNode: link ownerRule: null parentStyleSheet: null rules: CSSRuleList title: null type: "text/css" */ // Get the media type console.log(document.styleSheets[0].media.mediaText) /* Returns: "all" or "print" or whichever media is used for this stylesheet */
In any event, there are many ways to grab a stylesheet to attach style rules to.
Creating a New Stylesheet
In many cases, it may just be best to create a new STYLE
element for your dynamic rules. This is quite easy:
var sheet = (function() { // Create the <style> tag var style = document.createElement("style"); // Add a media (and/or media query) here if you'd like! // style.setAttribute("media", "screen") // style.setAttribute("media", "only screen and (max-width : 1024px)") // WebKit hack :( style.appendChild(document.createTextNode("")); // Add the <style> element to the page document.head.appendChild(style); return style.sheet; })();
Unfortunately WebKit requires a hack to properly get things going but all we care about is having that sheet.
Inserting Rules
Stylesheets have an insertRule
method which isn't available in earlier IE's but is now the standard for rule injection. The insertRule
requires that you write the entire CSS rule just as you would in a stylesheet:
sheet.insertRule("header { float: left; opacity: 0.8; }", 1);
This method may seem a bit ugly for a JavaScript API but that's how it works. The second argument, the index
, represents the index at which to insert the rule. This is helpful so that you can insert the same rule/code and define which wins out. The default for index is -1
, which means the end of the collection. For extra/lazy control, you may add !important
to rules to avoid problems with the index.
Adding Rules - Nonstandard addRule
CSSStyleSheet
objects have an addRule
method which allows you to register CSS rules within the stylesheet. The addRule
method accepts three arguments: the selector, the second the CSS code for the rule, and the third is the zero-based integer index representing the style position (in relation to styles of the same selector):
sheet.addRule("#myList li", "float: left; background: red !important;", 1);
addRule
calls return a result of -1
in all cases -- it really doesn't represent anything.
Remember that the advantage here is that elements added from the page automatically have the styles applied to them; i.e. you wont have to add them to elements as they're injected into the page. Efficient!
Safely Applying Rules
Since browser support for insertRule
isn't as global, it's best to create a wrapping function to do the rule application. Here's a quick and dirty method:
function addCSSRule(sheet, selector, rules, index) { if("insertRule" in sheet) { sheet.insertRule(selector + "{" + rules + "}", index); } else if("addRule" in sheet) { sheet.addRule(selector, rules, index); } } // Use it! addCSSRule(document.styleSheets[0], "header", "float: left");
This utility method should cover all cases of new style application. If you are nervous about applying variable styles across your app, it's reasonable to wrap the inner code of this method in a try{}catch(e){}
block.
Inserting Rules for Media Queries
Media query-specific rules can be added in one of two ways. The first way is through the standard insertRule
method:
sheet.insertRule("@media only screen and (max-width : 1140px) { header { display: none; } }");
Of course since IE hasn't always supported insertRule
, the other method is creating a STYLE
element with the proper media attribute, then adding styles to that new stylesheet. This may require juggling multiple STYLE
elements, but that's easy enough. I would probably create an object with media queries as indexes, and create/retrieve them that way.
Dynamically adding rules to stylesheets is efficient and easier than you may think. Keep this strategy in mind on your next big app, as it may save you work in both code and element processing.
Wow, could make it easier to parse custom css on the client. :)
Are there any reasons to why you didnt’t add addCSSRule to the CSSStyleSheet prototype? That way you could skip the first argument and add the rule to the CSS directly. Or if you just added it to “sheet”.
Not a horrible idea but then I’d get a barrage of hate for modifying prototypes ;)
This was a great catch I definitely love saving keystrokes.
Thanks
Would you prefer this approach rather than lazy loading a stylesheet by inserting it in the DOM for special use cases? I’m wondering it it’s creating some penalties other than reflows.
This would definitely cut down on DOM queries! Javascript is not my strongest suit. Your blog is very helpful.
Tips:
Inserting multiple statement at once:
e.g. you need to insert this rules:
Use this:
otherwise you will get parsing error.
I swear, dealing with style sheet rules is *a pain*!
There has never been consensus on the APIs in the past, essentially because the W3C never stated anything about it. So, as usualy, IE had a different set of APIs, but even among “standard” browsers like Firefox and Opera there were some differences on the way the rules are dealt, grouped and whatever.
Hopefully we’ll see something more consistent as IE8 usage becomes meaningless.
Tips:
In the case of older IE, you can use old technique of inserting CSS on the runtime which has been around for a very long time. Check out this article:
http://yuiblog.com/blog/2007/06/07/style/
@Mikael Söderström Adding properties to native objects is considered a bad (and dangerous) practice. A better approach would be creating a new object extending CSSStyleSheet (http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/). The only cases I would think of extending a built-in directly are shims.
Stylesheets have bigger impact on loading times, than they used before, but I don’t think it’s necessary to be so careful about them unless we are developing with some low-spec mobile devices in mind (I would consider optimizing the css selector structure and reduce number of DOM element at first). However, being able to create css rules this way makes creating and editing CSS
@keyframes
based animations a lot easier (and looks cleaner as well).* which are a lot more efficient than solutions using
requestAnimationFrame()
orsetInterval()
, also we still will have to wait for web animations.You should check out the interface dgrid has implemented for doing this (which also adds the ability to easily remove added rules and old IE support):
https://github.com/SitePen/dgrid/blob/master/util/misc.js#L84
dgrid uses dynamic stylesheets for quite a bit including column resizing.
It’s worth pointing out that this APIs are defined in the CSSOM spec:
http://dev.w3.org/csswg/cssom/
would you please elaborate it why we use it instead of css stylesheet.
You still use a CSS stylesheet; I’m saying you add rules to that stylesheet instead of directly applying styles to nodes.
Thanks for the article David. I have found this a useful technique, especially in prototyping. I wrapped this in a library call Csster [http://github.com/ndp/csster]. BTW I ended up moving away from adding individual rules and simply making the browser do all the parsing by inserting a STYLE block… it was just simpler.
Typo: a hack to property
Good article, thanks for another winner David.
Great explanation, thanks. How would you add
@-
rules, like@font-face
or@keyframes
?Hmm. An interesting approach but I’m not sure I see what the advantage is.
Your styles are now spread throughout your JS and are those addStyle calls really any less expensive than adding a class to the DOM?
Excellent article
That’s absolutely great but there is a question how can we remove rules from a selector?
Excellent question — I guess you just overwrite the rule (last one wins)? Again and again and again?
A few comments:
1. I’m a little skeptical of the claim that this is efficient, though that really depends on what you’re comparing it to. Dynamically adding rules to a style sheet leads to rerunning selector matching on the entire document, at least in Gecko. Maybe faster than running multiple querySelectorAll calls after anything changes, but that doesn’t mean it’s fast.
2. The
addRule
method you describe is not standard and not cross-browser. insertRule is standard. See http://dev.w3.org/csswg/cssom/#cssstylesheetFirefox (I have v25) does not have a addRule method for CSSStyleSheet. It instead has a insertRule method (which also exists in Chrome (I tested v31) and IE11. The syntax is different – it’s a full rule string like
body { background: red; }
And what about fontFaces? What’s the correct variant?
I had various issues with ie<9 with the above code. But I found that adding css specs to a new node just worked fine. With jQuery:
$('' + css + '').appendTo('head');
Of course, that doesn’t allow you to modify the rules later.
oops – code got mangled in the above comment. I meant:
without the square brackets.
I just wanted to say thank you. You helped me run my script 8-9 times as fast as before.
Ok so my code was riddled with mistakes :) 100% correct version, tested on this page and turned the text red:
Some years ago I got all excited about the performance implications of modifying the CSS rules and leaning on the browser’s internal machinery rather than doing lots of DOM queries and setting style properties in js. And in some cases it does turn out to be faster – table column widths was my canonical example. But when I went to write this up and did more cross-browser/platform testing to confirm my findings I found the results wildly variable – probably due to the issues dbaron points out: “Dynamically adding rules to a style sheet leads to rerunning selector matching on the entire document, at least in Gecko”
The upshot wasnt nearly as exciting as I hoped: it’s sometimes faster in some specific cases, in particular environments. YMMV and measure don’t guess.
That said, as far as patterns go, making presentation changes across a document by changing the stylesheet seems preferable to querying and manipulating style properties on the elements themselves. Maybe this rerunning selector matching on the entire document issue should be filed as a bug? It seems heavy-handed and like we could do a bit better. And a rudimentary selector API for CSSOM (find rules efficiently) wouldn’t hurt too.
Hi David
Thanks for this article, I love the ideas in it.
I noticed that when I tried using the code in this article, the Webkit hack was causing issues in IE but removing it didn’t seem to cause any issues. Can you remember which versions of Webkit were affected and what happens if the hack isn’t included?
Cheers
Skateside
A little improvement to the original addCSSRule function: Basically, it does not add additional cssRules for the same selector, instead it appends the rules to the existing one, and it returns the resulting cssRule.
This line in your Creating a New Stylesheet example is incorrect:
It should omit the ‘@media ‘ like the commented line before it does.
You should check out
veinjs
– a little script I wrote to do something very similar:https://github.com/israelidanny/veinjs
One thing worth noting about dealing with adding rules to the stylesheet: Prefixed properties may need special handling depending on your use-case.
If you add a rule with a property which uses a prefix like
-webkit
,-moz
or whatever, all the non-supported ones will get stripped out. So for example, you could add a-moz
prefixed property in Webkit, and it will simply strip it out.Thus, if you actually want to for example save the CSS you’ve generated for whatever reason, you’ll need to use some other approach to include the prefixed versions for other browsers.
Good point!
But when I test the performance in the Chrome timeline, I found that when using insertStyle/deleteStyle, it makes the
Recalculate Style
time more longer than change style directly on the DOM or change the classes, about 1ms to 0.3ms respectively in my simple test.And I look inside what’s going on when the browser is recalculating the style, I found that when I edit the style rule in the styleSheet, it forces the browser to recaculate all the style rules, and the worst thing is this performance gap is growing when my CSS rule is getting more complex.
If there’s some image that is base64 format, the browser need to parse all that images again, which makes it more slower. In my another test, which I have lots base64 images, the results of
Recaculate Style
time becomes 5ms to 0.3ms respectively.Hey, check this out:
http://jsperf.com/inject-vs-apply/8
I came up with a little library of my own that injects CSS into a style element , and I’m seeing amazing performance.
Great article, it’s helped me crack the back of a persistent problem I’ve had on a big web app build.
For my usage I needed to clear all the CSS rules each time I was going to update them so I decided to make a function to handle this. It might come in handy for anyone who wants to delete / remove rules.
Hey David, is there an event to listen about when the cssRules are parsed?
The latest version of IE(11.0.9600) still doesn’t support the
insertRule()
method on windows 7, useappendRule()
instead!I’m using
sheet.insertRule
in IE 11.0.9600.17420 on Windows 7 Ultimate 64-bit. Works just fine.Safari 8 and FF 34 don’t update styles with Webkit hack. Probably this hack is outdated. (Chrome 39 works well with and without this line.)
You can use wjs library for that. Once installed, use :
I think it’s worth saying that dynamically changing stylesheets this way may lead to some devtools weirdness.
For instance, in chrome, open a page that runs JS to update its own stylesheet. Then open the chromedevtools and select an element that has a CSS rule that was just changed.
You won’t see the changes in the Styles tab, instead you’ll see the unaltered, as-authored style.
The thing is it’s pretty important for browser devtools to show the styles as they have been authored. And dynamic updates to the CSS object-model aren’t easily reconciled with the original as-authored stylesheet.
Right now, firefoxdevtools does *not* show the styles as they were authored, so this scenario will work.
I can confirm this. I broke Chrome dev tools performing CSS injection: https://code.google.com/p/chromium/issues/detail?id=387952#c6
But why not use the Modernizr-approach by setting a class on html or body to set various things to how you want them. It seems a lot simpler to me and doesn’t require you to do any css hacking in the first place.
And i also like to suggest to use normal mediaqueries in the first place. If your content doesn’t do that right than you might think about doing these dirty solutions but i still don’t see why you would ever need any of this. It just seems like your classes are all wrong
That is a very great idea. But what about browser support David.
I wrote a small JS Perf, and as I was suspecting this is extremely unlikely to perform in any situation, manipulating the DOM directly is still the best thing to do (or even better just swap CSS classes)
http://jsperf.com/stylesheet-vs-dom-manipulation
Brilliance and simplicity go hand-in-hand. You need to write a book.
Inspired by this article, I’ve made this lib: https://github.com/cssobj/cssobj
Manipulate CSSOM and CSS Rules totally from JS
Also can see the demo page: https://github.com/cssobj/cssobj
Thanks! Used here: https://gist.github.com/chrisranderson/e62d844000b3f4e13e485e89ee4a2581
In what situations would adding a new stylesheet be better than manipulating classes with JS?
Mozilla says: “Add a stylesheet rule to the document (may be better practice, however,
* to dynamically change classes, so style information can be kept in
* genuine stylesheets (and avoid adding extra elements to the DOM))”
Using JS, how would I change what is inside an existing css class?
re “Stylesheets have an insertRule method which isn’t available in earlier IE’s but is now the standard for rule injection”
…you might be missing the fact that MSIE -6-7-8-… had
styleSheet
instead ofsheet
and there’s little documentation ‘out there’ to connect the two…it appears my (old) MSIE6-8 JScript
styleobj.styleSheet.rules[0].style
should be updated tostyleobj.sheet.rules[0].style
(very simple after hours of researching the Internet)I thought I would add some context to the discussion. I can think of two reasons why you need to style something using JavaScript rather than just doing the job in the Style Sheet and possibly using JavaScript to toggle things:
First, there may be some values which cannot be determined in a static style sheet. For example, you may set a colour dependent on the time of day, or some dimensions dependent on a loaded image. JavaScript is well suited to setting dynamic values.
Second, you may be writing a JavaScript widget which adds one or more elements to the page. You might then use JavaScript to complete the job and set suitable styles for the added elements.
This article offers a simple technique which is much cleaner that hacking the .style property.
Is there any way to save the changes that were made to the stylesheet?
I want to allow users to modify the styles and then save the changes made. Stylesheet is an external css file that I’m generating on the server so I need to send updated styles back either as plain css or an object that I can parse into css on the server
Thanks for this article, David. It’s helped me get past some very annoying limitations in a website builder I’m working with that doesn’t allow me to add custom CSS in any “normal” way. I’m going to be able to make a number of small tweaks using this method. However, I’m hitting a limitation with media queries. I’ve read the above about how they need to be handled differently, but I’m not a developer and don’t know enough about JavaScript to get it to work. Any suggestions on how to add in a simple media query per the below example?
The following works to make the site logo larger:
However, adding a media query in an attempt to only make the logo larger on large screen breaks it:
Nice talking about Stylesheets with JavaScript but there is compatibility issue with IE browser, and that create trouble for you.
I know this is a pretty old conversation, but you can actually just use innerHTML on the style element and it works
Caution: some of the information in this article is wrong (or outdated). Specifically,
-1
is not a valid index argument forinsertRule
(throws an exception). Also, it defaults to0
, not-1
when omitted. The correct index value for inserting a rule at end of sheet isstyleSheet.cssRules.length