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.
Thanks for the write-up. Looking forward for the next one.