1.
Scoped styles
It’s no secret that architecting CSS effectively at scale is incredibly difficult. When joining an existing long-lived project, it wasn’t uncommon to find that the CSS was the hardest part of the system to figure out.
To counter this, the CSS community has invested heavily in trying to address these issues, making our styles more maintainable with methodologies like OOCSS by Nicole Sullivan and SMACSS by Jonathan Snook—but the clear winner right now in terms of popularity seems to be BEM, or Block Element Modifier, from Yandex.
Ultimately, BEM (when applied purely to CSS) is just a naming convention, opting to limit styles to classes following a .Block__element--modifier pattern. In any given BEM-styled codebase, developers have to remember to follow BEM’s rules at all times. When strictly followed, BEM works really well, but why is something as fundamental as scoping left up to pure convention?
Whether they explicitly say so or not, the majority of CSS-in-JS libraries are following the BEM mindset of trying to target styles to individual UI elements, but implementing it in a radically different way.
What does this look like in practice? When using glamor by Sunil Pai, it looks something like this:
import { css } from 'glamor'
const title = css({
fontSize: '1.8em',
fontFamily: 'Comic Sans MS',
color: 'blue'
})
console.log(title)
// → 'css-1pyvz'
What you’ll notice here is that the CSS class is nowhere to be found in our code. It’s no longer a hard-coded reference to a class defined elsewhere in the system. Instead, it is generated automatically for us by the library. We don’t have to worry about our selectors clashing in the global scope, which means we no longer have to manually prefix them.
The scoping of this selector matches the scoping rules of the surrounding code. If you want to make this rule available to the rest of your application, you’ll need to turn it into a JavaScript module and import it wherever it’s used. In terms of keeping our codebases maintainable over time, this is incredibly powerful, making sure that the source of any given style can be easily traced like any other code.
By moving from mere convention towards enforcing locally-scoped styles by default, we’ve now improved the baseline quality of our styles. BEM is baked in, not opt-in.
—
Before I continue, there’s a critically important point to clarify.
This is generating real CSS, not inline styles.
Most of the earliest CSS-in-JS libraries attached styles directly to each element, but the critical flaw in this model is that ‘style’ attributes can’t do everything that CSS can do. Most new libraries instead focus on dynamic style sheets, inserting and removing rules at runtime from a global set of styles.
As an example, let’s have a look at JSS by Oleg Slobodskoi, one of the earliest CSS-in-JS libraries to generate real CSS.
JSS (Source)
When using JSS, you can use standard CSS features like hover styles and media queries, which map directly to the equivalent CSS rules.
const styles = {
button: {
padding: '10px',
'&:hover': {
background: 'blue'
}
},
'@media (min-width: 1024px)': {
button: {
padding: '20px'
}
}
}
Once you insert these styles into the document, the automatically generated classes are provided to you.
const { classes } = jss.createStyleSheet(styles).attach()
These generated classes can then be used instead of hard-coded class strings when generating markup in JavaScript. This pattern works regardless of whether you’re using a full-blown framework, or something as simple as innerHTML.
document.body.innerHTML = `
<h1 class="${classes.heading}">Hello World!</h1>
`
Managing the styles in this way is of little benefit by itself—it’s usually paired with some kind of component library. As a result, it’s typical to find bindings available for the most popular libraries. For example, JSS can easily bind to React components with the help of react-jss, injecting a small set of styles into each component while managing the global lifecycle for you.
import injectSheet from 'react-jss'
const Button = ({ classes, children }) => (
<button className={classes.button}>
<span className={classes.label}>
{children}
</span>
</button>
)
export default injectSheet(styles)(Button)
By focusing our styles around components, integrating them more closely at the code level, we’re effectively taking BEM to its logical conclusion. So much so that many in the CSS-in-JS community felt like the importance of extracting, naming and reusing components was being lost in all the style-binding boilerplate.
An entirely new way of thinking about this problem emerged with the introduction of styled-components by Glen Maddern and Max Stoiber.
Styled Components (Source)
Instead of creating styles, which we then have to manually bind to components, we’re forced to create components directly.
import styled from 'styled-components'
const Title = styled.h1`
font-family: Comic Sans MS;
color: blue;
`
When applying these styles, we don’t attach a class to an existing element. We simply render the generated component.
<Title>Hello World!</Title>
While styled-components uses traditional CSS syntax via tagged template literals, others prefer working with data structures instead. A notable alternative is Glamorous by Kent C. Dodds from PayPal.
Glamorous (Source)
Glamorous offers the same component-first API as styled-components, but opts for objects instead of strings, eliminating the need to include a CSS parser in the library—reducing the library’s size and performance footprint.
import glamorous from 'glamorous'
const Title = glamorous.h1({
fontFamily: 'Comic Sans MS',
color: 'blue'
})
Whichever syntax you use to describe your styles, they’re no longer just scoped to components—they’re inseparable from them. When using a library like React, components are the fundamental building blocks, and now our styles form a core part of that architecture. If we describe everything in our app as components, why not our styles too?
—
For seasoned veterans of BEM, all of this may seem like a relatively shallow improvement given the significance of the change we’ve introduced to our system. In fact, CSS Modules lets you achieve this without leaving the comfort of the CSS tooling ecosystem. This is why so many projects stick with CSS Modules, finding it sufficiently solves most of their problems with CSS at scale without sacrificing the familiarity of regular CSS.
However, it’s when we start to build on top of these basic concepts that things start to get a lot more interesting.