Add Rules to Stylesheets with JavaScript

By on  

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


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


	cssRules: CSSRuleList
	disabled: false
	href: ""
	media: MediaList
	ownerNode: link
	ownerRule: null
	parentStyleSheet: null
	rules: CSSRuleList
	title: null
	type: "text/css"

// Get the media type
	"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 :(

	// Add the <style> element to the page

	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.


Recent Features

  • Create Namespaced Classes with MooTools

    MooTools has always gotten a bit of grief for not inherently using and standardizing namespaced-based JavaScript classes like the Dojo Toolkit does.  Many developers create their classes as globals which is generally frowned up.  I mostly disagree with that stance, but each to their own.  In any event,...

  • Creating Scrolling Parallax Effects with CSS

    Introduction For quite a long time now websites with the so called "parallax" effect have been really popular. In case you have not heard of this effect, it basically includes different layers of images that are moving in different directions or with different speed. This leads to a...

Incredible Demos

  • Facebook-Style Modal Box Using MooTools

    In my oh-so-humble opinion, Facebook's Modal box is the best modal box around. It's lightweight, subtle, and very stylish. I've taken Facebook's imagery and CSS and combined it with MooTools' awesome functionality to duplicate the effect. The Imagery Facebook uses a funky sprite for their modal...

  • dwClickable:  Entire Block Clickable Using MooTools 1.2

    I recently received an email from a reader who was really impressed with Block Clickable, a jQuery script that took the link within a list item and made the entire list item clickable. I thought it was a neat script so I...


  1. 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 ;)

  2. 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.

  3. This would definitely cut down on DOM queries! Javascript is not my strongest suit. Your blog is very helpful.

  4. Fred


    Inserting multiple statement at once:

    e.g. you need to insert this rules:

    selector1 { rule : value } selector2 { rule : value }

    Use this:

    sheet.insertRule("@media all { selector1 { rule : value } selector2 { rule : value } }", 0); 

    otherwise you will get parsing error.

  5. MaxArt

    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.

  6. Fred


    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:

  7. @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 ( 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() or setInterval(), also we still will have to wait for web animations.

  8. Bryan Forbes

    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):

    dgrid uses dynamic stylesheets for quite a bit including column resizing.

  9. It’s worth pointing out that this APIs are defined in the CSSOM spec:

  10. 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.

  11. Thanks for the article David. I have found this a useful technique, especially in prototyping. I wrapped this in a library call 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.

  12. Publius

    Typo: a hack to property

  13. Good article, thanks for another winner David.

  14. Leon de Rijke

    Great explanation, thanks. How would you add @-rules, like @font-face or @keyframes?

  15. 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?

  16. Tony

    Excellent article

  17. Hooman

    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?

  18. 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

  19. Yaron Shapira

    Firefox (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; }

  20. And what about fontFaces? What’s the correct variant?

  21. pike

    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.

  22. pike

    oops – code got mangled in the above comment. I meant:

    $('[style type="text/css"]' + css + '[/style]').appendTo('head'); 

    without the square brackets.

  23. Tim

    I just wanted to say thank you. You helped me run my script 8-9 times as fast as before.

  24. Ok so my code was riddled with mistakes :) 100% correct version, tested on this page and turned the text red:

    // get a stylesheet
    var styles = document.styleSheets[0];
    // we insert an empty rule just to create a new CSSStyleRule object
    // the second param is the index to insert at
    // using the length property we effectively "append" the rule to the end of the sheet
    var ruleIndex = styles.insertRule("p {}", styles.cssRules.length)
    // the index is the position the rule was inserted at, so we can now get a handle on it
    var rule = styles.cssRules.item(ruleIndex);
    // now we have it, modify it's styles!
    // applied to the document immediately = "red";
  25. 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.

  26. 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?


  27. 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.

      var addCSSRule = function(sheet, selector, rules){
        //Backward searching of the selector matching cssRules
        var index=sheet.cssRules.length-1;
        for(var i=index; i>0; i--){
          var current_style = sheet.cssRules[i];
          if(current_style.selectorText === selector){
            //Append the new rules to the current content of the cssRule;
   + rules;
          sheet.insertRule(selector + "{" + rules + "}", index);
          sheet.addRule(selector, rules, index);
        return sheet.cssRules[index].cssText;
  28. John Madhavan-Reese

    This line in your Creating a New Stylesheet example is incorrect:

    // style.setAttribute("media", "@media only screen and (max-width : 1024px)")

    It should omit the ‘@media ‘ like the commented line before it does.

  29. You should check out veinjs – a little script I wrote to do something very similar:

  30. 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.

  31. 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.

  32. 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.

    function clearCSSRules(sheet){
      var i = sheet.cssRules.length - 1 ;
      // Remove all the rules from the end inwards.
      while(i >= 0){
        if("deleteRule" in sheet) { sheet.deleteRule(i); }
        else if("removeRule" in sheet) { sheet.removeRule(i); }
  33. Hey David, is there an event to listen about when the cssRules are parsed?

  34. Kevin

    The latest version of IE(11.0.9600) still doesn’t support the insertRule() method on windows 7, use appendRule() instead!

    • John Madhavan-Reese

      I’m using sheet.insertRule in IE 11.0.9600.17420 on Windows 7 Ultimate 64-bit. Works just fine.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!