Messing with CSS through its JavaScript API
Tags: webdev, javascript, css
Published: 2019-05-29 13:38:08

**This post is taken from [my blog](https://areknawo.com), so be sure to check it out for more up-to-date content.**

Beyond all preprocessors, transpiler, or whatever web development tool you can think of, one fact still remains true - it's  **HTML**,  **CSS**, and  **JavaScript**  that power today's web. Any kind of tool, language and etc., all that remains dependent on these 3 technologies (if we don't count the uprising WebAssembly). They work and interact together, to provide you with limitless possibilities to create newer, better and even more stunning things!

JavaScript is - if we can call it that way - the king of interactivity. Its capabilities as a scripting language itself, combined with numerous  **web APIs**  extending its feature-set even further, are truly impressive. Examples of such APIs include the most well-known WebGL API, Canvas API, DOM API, but also a lesser-known set of CSS-related methods, which can be called (unofficially)  **CSS API**. And that's what we're going to explore in today's post!

While the idea of interacting with DOM through its JS API was made really popular thanks to concepts such as JSX and countless JS frameworks, the use of similar techniques with CSS doesn't seem to have that much attention. Of course,  **CSS-in-JS**  solutions exist, but the most popular ones are rather based on  **transpilation**, outputting CSS without any additional runtime in production. That's good for performance for sure, as CSS API usage may cause [_additional reflows_](https://areknawo.com/dom-performance-case-study/), which makes it just as demanding as the use of DOM API. But still, this isn't what we're looking for. What if I tell you that you can not only manipulate DOM elements' styles and CSS classes but also create full-blown  **stylesheets**, just like with HTML, just with the use of JS?

# Basics

## Inline styles

Before we'll dive deep into the complex stuff, let's first remind ourselves some basics. Like the fact that you can edit the given  `HTMLElement`'s  **inline styles**  by modifying its  `.style`  property.

```javascript
const el = document.createElement("div");

el.style.backgroundColor = "red";
// or
el.style.cssText = "background-color: red";
// or
el.setAttribute("style", "background-color: red");
```

Setting your style properties directly on the  `.style`  object will require you to use  _camelCase_  as your property keys, instead of  _kebab-case_. If you have much more inline style properties to set (although, in such case, you may consider using CSS classes), you can do this in a bit more performant way, by setting the  `.style.cssText`  property or by setting the  `style`  attribute. Keep in mind that this will completely reset the inline styles of your element, and thus, requires you to include  **all properties**  (even the unchanged ones) in the string argument. If such micro-optimizations don't interest you (they really shouldn't), and your targeting modern browsers, you can consider using  `.style`  with  `Object.assign()`, to set multiple style properties at once.

```javascript
// ...
Object.assign(el.style, {
    backgroundColor: "red",
    margin: "25px"
});
```

There's a bit more to these  _"basics"_  than you'd probably think of. The  `.style`  object implements the  `CSSStyleDeclaration`  interface. This means that it comes with some interesting properties and methods! This includes known to us  `.cssText`, but also  `.length`  (number of set properties), and methods like  `.item()`,  `.getPropertyValue()`  and  `.setPropertyValue()`, allowing you to operate on inline styles, without the use of  _camelCase_, and thus - any case conversion. You can find the complete API  [documented on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration).

```javascript
// ...
const propertiesCount = el.style.length;
for(let i = 0; i < propertiesCount; i++) {
    const name = el.style.item(i); // e.g. "background-color"
    const value = el.style.getPropertyValue(name); // e.g. "red"
    const priority = el.style.getPropertyPriority(name); // e.g. "important"
    
    if(priority === "important") {
        el.style.removeProperty();
    }
}
```

Just a small tidbit - the  `.item()`  method that's most useful during iterations, has the alternate syntax in the form of  **access by index**.

```javascript
// ...
el.style.item(0) === el.style[0]; // true
```

## CSS classes

Now, let's leave inline styles for a moment and take a look at higher structures -  **CSS classes**. The basics include the  [`.className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className)  which has a form of a string when retrieved and set.

```javascript
// ...
el.className = "class-one class-two";
el.setAttribute("class", "class-one class-two");
```

Another way of setting classes string is by setting the  `class`  attribute (same for retrieval). But, just like with  `.style.cssText`  property, setting  `.className`  would require you to include all classes of the given element in the string, including the changed and unchanged ones. Of course, some simple string operations can do the job, but surely there has to be another way... And there is! It's provided to us in the form of slightly-newer  `.classList`  property. By  _"slightly newer"_  I mean that it  [**isn't supported by IE 9**](https://caniuse.com/#feat=classlist), and only partially supported by IE 10 and IE 11.

The  `.classList`  property implements  [`DOMTokenList`](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList), giving you access to a whole bunch of useful methods. Likes of  `.add()`,  `.remove()`,  `.toggle()`  and  `.replace()`  allow you to change the current set of CSS classes, while others, e.g.  `.item()`,  `.entries()`  or  `.forEach()`  simplify the  **iteration process**  of this index collection.

```javascript
// ...
const classNames = ["class-one", "class-two", "class-three"];
classNames.forEach(className => {
    if(!el.classList.contains(className)) {
        el.classList.add(className);
    }
});
```

# Stylesheets

Now that we're done with the revision, let's start creating our JS-only stylesheet! First, let's break down all the details behind what's going on.

Going from top to the bottom, we have the  [`StyleSheetList`](https://developer.mozilla.org/en-US/docs/Web/API/StyleSheetList)  interface, implemented by  `document.styleSheets`  property. It helps to represent the situation seen in standard HTML code - the use of  **multiple stylesheets**  in one document. Whether it's from an external file, URL or within  `<style/>`  tag,  `document.styleSheets`  collects them all in an indexed collection, implementing standard iteration protocols. With that said, you can access all the  [`CSSStyleSheet`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet)s with a simple loop.

```javascript
for(styleSheet of document.styleSheets){
	console.log(styleSheet);
}
```

As that's all there is to  `StyleSheetList`, let's go over to  `CSSStyleSheet`  itself. It's here where things start to get interesting!  `CSSStyleSheet`  extends  [`StyleSheet`](https://developer.mozilla.org/en-US/docs/Web/API/StyleSheet)  interface, and, with this relation come only a few  _read-only_  properties, like  `.ownerNode`,  `.href`,  `.title`  or  `.type`, that are mostly taken straight from the place where given stylesheet was declared. Just recall the standard HTML code for  **loading external CSS**  file, and you'll know what I'm talking about.

```html
<head>
<link rel="stylesheet" type="text/css" href="style.css" title="Styles">
</head>
```

So, all the stuff that interests us the most is inside the  `CSSStyleSheet`  interface. Now, we know that HTML document can contain multiple stylesheets, and now... all these stylesheets can contain different rules or even more stylesheets (when using  `@import`) within them! And that's the point we're at.  `CSSStyleSheet`  gives you access to two methods -  `.insertRule()`  and  `.deleteRule()`.

```javascript
// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
styleSheet.deleteRule(ruleIndex);
```

These methods operate with indices and  **CSS-like strings**. As CSS rules order is important to decide which one should be used in case of conflict,  `.insertRule()`  allows you to pass an optional index for your new rule. Know that some misuses may result in an error, so... just keep it simple.

`CSSStyleSheet`  also has two properties of its own -  `.ownerRule`  and  `.cssRules`. While  `.ownerRule`  is related to  `@import`  stuff, it's the second one - the  `.cssRules`  - that interests us the most. Simply put, it's a  [`CSSRuleList`](https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList)  of  [`CSSRule`](https://developer.mozilla.org/en-US/docs/Web/API/CSSRule)s, that can be modified with earlier-mentioned  `.insertRule()`and  `.deleteRule()`  methods. Keep in mind, that some browsers may  **block**  you from accessing  `.cssRules`  property of external  `CSSStyleSheet`  from a different origin (domain).

So, what about `CSSRuleList`? Again, it's an iterable collection of  `CSSRule`s, meaning that you can iterate over it, access its  `CSSRule`s by their indices or  `.item()`  method. What you cannot do though is modifying  `CSSRuleList`  directly. It can only be done with previously mentioned methods and nothing else.

The  `CSSRuleList`  contains object implementing  `CSSRule`  interface. This one comes with properties such as  `.parentStyleSheet`  and - most importantly -  `.cssText`, containing all the CSS code of the given rule. There's yet one more interesting property -  `.type`. It indicates the type of given  `CSSRule`, according to  [specified constants](https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants). You should remember, that besides the most often use  _"standard"_  style-related rules, CSS can consist of e.g.  `@import`  or  `@keyframes`  (most notably) rules.  `CSSRule`s of different types have corresponding interfaces. As you won't be creating them directly, but rather with CSS-like strings, you don't really have to know anything more, that the properties that these extended interfaces provide.

In case of the  [`CSSStyleRule`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule), these properties are  `.selectorText`  and  `.style`. First one indicates the selector used for the rule in the form of a string, and the second one is an object implementing  `CSSStyleDeclaration`  interface, which we've discussed before.

```javascript
// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
const rule = styleSheet.cssRules.item(ruleIndex);

rule.selectorText; // "div"
rule.style.backgroundColor; // "red"
```

# Implementation

At this point, I think we know enough about CSS-related JavaScript APIs, to create our own, tiny,  **runtime-based**  CSS-in-JS implementation. The idea is that we'll create a function, that passed a simple style configuration object, will output a hashed name of newly created CSS class for later use.

So, our workflow here is pretty simple. We need a function that has access to some sort of stylesheet and just use  `.insertRule()`  method together with phrased style config to make everything ticking. Let's start with the stylesheet part.

```javascript
function createClassName(style) {
  // ...
  let styleSheet;
  for (let i = 0; i < document.styleSheets.length; i++) {
    if (document.styleSheets[i].CSSInJS) {
      styleSheet = document.styleSheets[i];
      break;
    }
  }
  if (!styleSheet) {
    const style = document.createElement("style");
    document.head.appendChild(style);
    styleSheet = style.sheet;
    styleSheet.CSSInJS = true;
  }
  // ...
}
```

If you're using  **ESM**  or any other kind of JS module system, you can safely create your stylesheet instance  **outside the function**  and not worry about other people accessing it. But, as I wanted to keep this example minimal, we'll just set the  `.CSSInJS`  property on our stylesheet as a form of a  **flag**, informing us if this is the one we want to use.

That's pretty much all about the first part of the code snippet above. Now, what if we have to create a new stylesheet for our purposes? There's no straight-forward way of doing this. Our best bet would be to create a new  `<style/>`  tag and append it to our HTML document's  `<head/>`section. This automatically adds a new stylesheet to the  `document.styleSheets`  list and allows us to access it by the  `.sheet`  property of our  `<style/>`  tag. Pretty clever, huh?

```javascript
function createRandomName() {
  const code = Math.random().toString(36).substring(7);
  return `css-${code}`;
}

function phraseStyle(style) {
  const keys = Object.keys(style);
  const keyValue = keys.map(key => {
    const kebabCaseKey = 
        key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
    const value = 
        `${style[key]}${typeof style[key] === "number" ? "px" : ""}`;
    
    return `${kebabCaseKey}:${value};`;
  });
  
  return `{${keyValue.join("")}}`;
}
```

Actually, beyond the tiny tidbit above, there's really no more similarly-interesting stuff going on. Naturally, we first need a way to generate a new,  **random name**  for our CSS class. Then, we need to properly phrase our style object, to the form of viable CSS string (or at least part of it). This includes the conversion between  _camelCase_  and  _kebab-case_, and, optionally, handling of  **pixel unit**(px)  **conversion**. Oh, and don't forget  **the semicolon**  (`;`) at the end of every key-value pair!

```javascript
function createClassName(style) {
  const className = createRandomName();
  let styleSheet;
  // ...
  styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
  return className;
}
```

Then, we go to our main function and make the required adjustments. We generate the random name and insert the CSS rule to the stylesheet. As all out rules are about classes, they all require a dot on their respective beginning for a  **proper selector**. Believe me, it's super easy to forget!

```javascript
const redRect = createClassName({
  width: 100,
  height: 100,
  backgroundColor: "red"
});

el.classList.add(redRect);
```

With all set and done, we can finally put our code to the final test! Everything should work just fine! Below is a CodePen to prove that.

{% codepen https://codepen.io/areknawo/pen/zQjyom %}

# What do you think?

As you can see, manipulating CSS from JavaScript level is very interesting. Whether you know it's possible or not, you must admit - it's pretty awesome. Our little example above is only a  **proof-of-concept**. There's a lot more potential within CSS API (or rather APIs). And it's just waiting to be unveiled!

So,  **what do you think**  of this post? I'd love to see your  **opinions**,  **comments**, and  **reactions** below! Also, if you like articles like this one, consider  **following me**  [**on Twitter**](https://twitter.com/areknawo),  [**on my Facebook page**](https://facebook.com/areknawoblog), and checking out my [**personal blog**](https://areknawo.com). Again, thank you very much for reading this one, and I hope you're having a  **wonderful day**!