Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
css-in-js

A series of posts on css-in-js

0. styles as objects

First, an exercise. Can we represent all of css with plain data? Let's try.

let redText = { color: 'red' };

This is nice, it's fairly obvious what this code 'does'. Let's make another.

let boldText = { fontWeight: 'bold' };

Still pretty clean. We've switched to camelCase for property names (as opposed to css hyphen-case), but that seems super natural in javascript, and preferred. Now, Let's combine the two.

let boldRedText = { ...redText, ...boldText };

Nothing special, it's "just javascript". you can inspect it and see what it contains, no surprises.

console.log(boldRedText);
// { color: 'red', fontWeight: 'bold' }

An alternate representation of combining the two with an array -

let boldRedText = [redText, boldText];

console.log(boldRedText);

// [{ color: 'red' }, { fontWeight: 'bold' }]

This representation has the advantage of preserving the pieces and order that compose the style, which is nice for debugging, while also being more efficient for the computer to handle (we'll dive into optimisations in a later post).

Let's extend this object language further by adding pseudo selectors -

let redGreenText = {
  color: 'red',
  ':hover': {
    color: 'green',
  },
};

It should hopefully be clear what this object represents - a text style text that's red by default, and green when hovered. Like before, this composes well.

let composed = [redGreenText, boldText];
/*
[{
  color: 'red',
  ':hover': {
    color: 'green'
  }
}, {
  fontWeight: 'bold'
}]

this would be equivalent to -

{
  color: 'red',
  fontWeight: 'bold',
  ':hover': {
    color: 'green'
  }
}
*/

Now say we wanted bold text only on hover; how would we represent that?

let composed = [redGreenText, { ':hover': boldText }];
/*
this would be equivalent to -
{
  color: 'red',  
  ':hover': {
    color: 'green',
    fontWeight: 'bold'
  }
}
*/

Nice!

We can nest more than just pseudo classes. Sass/Less folks will be familiar with contextual selectors -

let translucentRed = {
  backgroundColor: 'rgba(255, 0, 0, 0.8)',
  '.ie6 &': {
    backgroundColor: 'red',
  },
};

This would mean "background color is a translucent red, unless it has a parent with class ie6, in which case it's plain red."

Similarly, we can add support for @media queries and @supports blocks. A contrived example showing them all in one object -

let page = {
  color: 'red',
  ':hover': {
    color: 'blue',
  },
  '@media screen': {
    color: 'blue',
    '@supports (display: flex)': {
      color: 'yellow',
    },
  },
};

You're free to nest arbitrarily and deeply; with whatever combination of selectors or media queries or whatnot.

Finally, sometimes, you want to output multiple values/fallbacks for a property. It only feels natural to define this with arrays. For example, suppose you need background color that's translucent red in browsers that support rgba(), and plain red in older browsers.

let style = {
  color: ['red', 'rgba(255, 0, 0, 0.8)'],
};

With these few rules, we can write css styles that target the entire css spec, no exceptions.

Because they're plain javascript objects, there's no third party dependency to import, or fancy syntax, and runs in any javascript environment. You can even type-check the objects with flow/typescript/etc. Leverage decades of data management knowledge and 'architect' your styles in whatever manner you prefer!

Indeed, you're now free to implement any of the 'classic' css architectures like itcss, smacss, oocss, bem, but without any of the constraints of a statically compiled language like sass/less. Further, because we're colocating these styles in a dynamic environment, we can create completely new and powerful abstractions like glamorous/styled-components, jsxstyle, and so on. We'll explore these architectures and abstractions in a future post.

So far, so good. Now how do we use these styles? Keen observers would have also noticed the lack of any html, or classnames to add to elements.

Let's assume we have a magical function, css(), that takes any of these objects and gives us a classname.

let cls = css({color:'red'});
let html = `<div class='${cls}'> this is red! </div>`;
// <div class='css-1sdfpa'> this is red! </div>

... that's it. there's nothing new to 'learn', and it cooperates really well with the rest of your tooling/workflow/ecosystem. Brilliant!

In the next post, we'll dive into what css() does behind the scenes.

@itaditya

This comment has been minimized.

Copy link

commented Jan 21, 2018

such a neat article, can't wait for the next one

@dhruvdutt

This comment has been minimized.

Copy link

commented Jan 21, 2018

Neat! 💯

@manti

This comment has been minimized.

Copy link

commented Jan 21, 2018

👍

@JakeCoxon

This comment has been minimized.

Copy link

commented Jan 21, 2018

You can also use babel-plugin-css-to-js if you prefer to write actual CSS that gets transformed into a js object

@hookdump

This comment has been minimized.

Copy link

commented Jan 21, 2018

Neat!!!

@stereobooster

This comment has been minimized.

Copy link

commented Jan 21, 2018

👏 👏 👏 Here is my try to explain some css-in-js

@amincharoliya

This comment has been minimized.

Copy link

commented Jan 22, 2018

Well explained. Looking for next one!

@akonchady

This comment has been minimized.

Copy link

commented Jan 22, 2018

Thanks for the write-up. Looking forward for the next one.

@TristanAG

This comment has been minimized.

Copy link

commented Jan 22, 2018

Very cool!

@dealonzo

This comment has been minimized.

Copy link

commented Jan 22, 2018

Amazing!

@karanpvyas

This comment has been minimized.

Copy link

commented Jan 22, 2018

ha ha ha

@SanthoshRaju91

This comment has been minimized.

Copy link

commented Dec 8, 2018

Nice writeup, waiting for the next one css()

@maarekj

This comment has been minimized.

Copy link

commented Dec 14, 2018

const rule = {
    margin: 0,
    marginTop: 10,
};

This rule is problematic. Because it can generate this css:

.rule {
    margin: 0;
    marginTop: 10px;
}

or this one:

.rule {
    marginTop: 10px;
    margin: 0;
}

and the two are not equivalent.

To avoid ambiguity, it would be necessary to write:

const rule = [
    ["margin", 0],
    ["marginTop", 10],
];

// or

const rule = [
    {margin: 0},
    {marginTop: 10},
];
@vetras

This comment has been minimized.

Copy link

commented Dec 17, 2018

I found this out today :)
but I can't find the next post in the series.
Has there ever been a next post? :)

@sumit-gupta91

This comment has been minimized.

Copy link

commented Feb 16, 2019

Well explained. Waiting for next one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.