Add Rules to Stylesheets with JavaScript

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.