Skip to content

Instantly share code, notes, and snippets.

@threepointone
Last active March 21, 2023 01:53
Show Gist options
  • Save threepointone/9f87907a91ec6cbcd376dded7811eb31 to your computer and use it in GitHub Desktop.
Save threepointone/9f87907a91ec6cbcd376dded7811eb31 to your computer and use it in GitHub Desktop.
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.

@AdarshKonchady
Copy link

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

@TristanAG
Copy link

Very cool!

@dealonzo
Copy link

Amazing!

@karanpvyas
Copy link

ha ha ha

@SanthoshRaju91
Copy link

Nice writeup, waiting for the next one css()

@maarekj
Copy link

maarekj 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
Copy link

vetras 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
Copy link

Well explained. Waiting for next one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment