Announcing šŸ’… styled-components v2: A smaller, faster drop-in upgrade with even more features

Max Stoiber
šŸ’… styled-components
8 min readMay 25, 2017

--

After a six month long iteration process and over 300 commits weā€™re super excited to announce the official release of styled-components v2!

TL;DR

  • Half the bundle size, (22kB ā†’ 12kB) thus much faster to load for your users
  • Best-in-class server-side rendering with critical CSS, style rehydration and concurrency support out of the box
  • Weā€™ve added the.attrs helper to support passing attributes to any styled-component, making it much easier to integrate existing CSS codebases.
  • Weā€™ve added an .extend and a .withComponent helper to make it easier to create multiple components that share some or all of their CSS.
  • You can now reference another component in your CSS, for cases where you really need to make a contextual override.
  • We made an optional Babel plugin which allows to add your component names in the class names and minifies your styles in production
  • v2 is a drop-in upgrade for most people so you can start using it with confidence
  • We also have a new website with completely overhauled documentation
  • Lots of other bug fixes and improvements, like static per-component class names for easier editing of styles from the DevTools

Read on to learn more about the changes weā€™ve made and why this is a semver-major release.

Upgrading

v2 is a drop-in upgrade, so none of the public API surface has change. This is all you need to do to upgrade your app built with v1:

npm install --save styled-components

Details

Server-Side Rendering

After a very, very, very long discussion about the right approach weā€™ve finally added server-side rendering support ā€” but itā€™s not just your standard ā€œget a stringā€-kinda support. This is the real deal.

When you server-side render an app with šŸ’… styled-components it will automatically only send the critical CSS down to the client. We know which components you render, so we donā€™t send a single character of unnecessary CSS to your users.

On top of that we support concurrent server-side rendering to avoid blocking your process with every request and we automatically rehydrate your styles in the browser. That means we donā€™t inject any CSS in the browser after a server-side render if itā€™s already been injected on the server!

If youā€™re now thinking ā€œPff, that sounds super complex, Iā€™m sure thatā€™ll take me weeks to setup!ā€ I have some good news for you. All it takes is four lines of code:

import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'

const sheet = new ServerStyleSheet()
const html = renderToString(sheet.collectStyles(<YourApp />))
const css = sheet.getStyleTags()

Four lines of code and you get server-side rendering with automatic critical CSS detection, style rehydration on the client and concurrency support. Booyakasha.

Note: In some cases you can have checksum mismatches on the client and the server due to generated classNames changing (e.g. when doing code splitting). Donā€™t worry though, installing our Babel plugin will solve this.

The .attrs Helper

One of the goals of styled-components is to provide an interface thatā€™s familiar to folks who know CSS well. We donā€™t want to hide or remove anything! Thatā€™s why we use string literals instead of objects, and with v2 you can now pass static classnames to your components:

const Header = styled.header.attrs({
className: 'p2 bold white bg-blue'
})`
${ props => props.shadow && 'box-shadow: 0 2px 2px 0 #aaa;' }
`

This way, if youā€™ve already got some global ā€œbackboneā€ styles (like Basscss above) you can use styled-components to create & customise individual components while still defining the bulk of your styles by adding and removing static classes.

.attrs isn't just limited to className though, you can use it to set default attributes on your styled components:

const ExternalLink = styled.a.attrs({
target: '_blank',
rel: 'nofollow'
})`
/* ... */
`

This is also useful for the common pattern of passing a class to a different property than className to conditionally style it. A good example is react-router s Link component, which takes an activeClassName :

const activeClassName = randomstring()
const StyledLink = styled(Link).attrs({
activeClassName
})`
color: papayawhip;
// When the Link is active change the color to palevioletred
&.${activeClassName} {
color: palevioletred;
}
`

And, because itā€™s driven off props the same way the CSS blocks are, you can do things like use inline styles for rapidly-changing values:

const ColorPicker = styled.div.attrs({
style: props => ({
background: `${props.hue},
${50 + props.luminosity / 2},
${props.luminosity}`
})
})`
/* ... */
`
<ColorPicker hue={ mousePositionX } luminosity={ mousePositionY } />

This makes styled-components more flexible, and more useful as a result!

The .extend and .withComponent helpers

Sometimes, a single styled-component with lots of props isnā€™t the best way to do thingsā€”it can be better to build up a family of related components:

const Heading = styled.h1`
font-size: 2.5rem;
font-weight: bold;
margin: 2rem;
text-align: center;
`
const MarketingHeading = Heading.extend`
background: midnightblue;
color: white;
&:hover { text-shadow: 0 0 4px white; }
`

Both of these components share a set of styles, but the MarketingHeading adds several additional rules, and may diverge further in future. We could have used a prop on Heading to add these extra rules, but sometimes having two components is preferable. Previously, we would have done this with the following syntax:

const MarketingHeading = styled(Heading)`
/* ... */
`

This still works, but will now add two classnames to your component (one for MarketingHeading and one for Heading), and both Heading and MarketingHeading will be in the React render tree. Using extend is cleaner and faster because you donā€™t get extra class names or components.

Another common request is the ability to change the HTML tag depending on context. Thereā€™s several long discussions on this topic on the issue tracker, but this is now made possible with the withComponent helper:

const Heading = styled.h1`
/* ... */
`
export const H1 = Heading
export const H2 = Heading.withComponent('h2')
export const H3 = Heading.withComponent('h3')
export const LinkHeading = Heading.withComponent(Link)

Contextual Overrides

One of the deliberate design goals of styled-components has been to make it easy to keep the styles for each component totally isolated, something that weā€™ve found makes the resulting CSS more for large applications. There are, however, definite instances where you need to break this rule, and have one component affect the appearance of another:

const Link = styled.a`
color: inherit;
text-decoration: none;
&:hover, &:active {
text-decoration: underline;
}
`
const P = styled.p`
${ Link } {
text-decoration: underline;
}
`

We recommend using this sparingly, as there are usually alternatives (passing a prop or using a theme, for instance) that are more explicit and, therefore, easier to reason about as your application becomes large. But in the cases where no alternative is available, itā€™s a useful escape to have.

The Babel Plugin

Since šŸ’… styled-components works at runtime we cannot know the names of your components and thusly cannot add them to the generated class names (like you would be used to with CSS modules).

To circumvent this limitation weā€™ve built a Babel plugin which allows us to generate classes that include your componentsā€™ names! While we were at it we also made sure the correct names show up in the React DevTools, so rather than seeing <styled.div> or <styled(Component)> youā€™ll now see <Wrapper> and <styled(Link)> !

On top of that the plugin also minifies your styles and helps us avoid checksum mismatches when server-side rendering.

All of that being said, itā€™s important to mention that this plugin is 100% optional. One of šŸ’… styled-componentsā€™ big goals was to work without any build step, and weā€™re committed to keeping it that way. That doesnā€™t mean we canā€™t get ourselves a little bit of enhancements at build-time thoughā€¦ šŸ˜‰

Thanks to Vladimir Danchenkov for his immense amount of help with this.

The Parser

For v1 of styled-components Glen and Max decided to take PostCSS, the popular CSS parser, rip out all of its Node.js specific code and use it to parse our CSS. PostCSS operates on an Abstract Syntax Tree (AST), which is useful if you want to have a plugin system.

Basically, PostCSS (as much as we ā¤ļø it) was not made for the browser and has a bunch of super cool features that styled-components doesnā€™t really need. That culminated in lots of unnecessary code being sent down to the client.

For version 2 we teamed up with @thysultan to create a CSS parser tailor-made for running in the browser called stylis. By doing so we managed to cut our bundle size (min+gzip) from ~24kB down to ~12kB, meaning youā€™ll now ship a lot less code to your users!

The reason we made this a semver major version rather than just releasing as v1.5 is because we cannot be 100% certain stylis has full feature-parity with PostCSS. Some bits and pieces of your old CSS might break, so double check your application after upgrading.

That being said, weā€™ve been trialling v2 in production for multiple months now to make sure stylis is as solid as can be and we know itā€™s 99.9% compatible. (beware of the missing semicolons though šŸ˜‰)

New Website and Documentation

Phil has been hard at work making our all new styled-components.com! Weā€™ve revamped the whole website and moved all of our documentation over there to make it more easily search-and scannable. (Itā€™s a server-side rendered React app, based on Next.js, built with ā€” you guessed it ā€” styled-components)

Contributors

Massive thanks to Phil PlĆ¼ckthun, Vladimir Danchenkov, @thysultan, Konstantin Pschera, Bruno Lemos and Matthieu Lemoine for heavily contributing to this release.

Also thanks to everybody who submitted issues or pull requests, chimed into one of our discussions or had some new ideas.

šŸ’… styled-components would not be as great as it is without you, the community. Thank you.

Future

Just in case you now think ā€œThatā€™s itā€, weā€™re far from done with šŸ’… styled-components. Here are some things weā€™re currently thinking about and will work on in the future:

Pre-processing (currently in alpha)

While writing our own CSS parser thatā€™s leaner and meaner was a good first step, why should we have to parse CSS at runtime at all?

To that effect Phil has been investigating pre-processing the style strings with the Babel plugin. There is an early, experimental version of this out already, please try it out (by enabling the preprocessoption of the Babel plugin) and let us know if you run into any trouble!

Static CSS extraction

To take this a step further, why do we have to inject CSS at runtime? Theoretically, it should be possible to extract a static .css file at build-time that includes all the static styles.

This has a bunch of unexplored implications and limitations which we arenā€™t fully aware of just yet, but weā€™ll start experimenting with this soon. We canā€™t promise this will ever be useable, but itā€™s definitely on our minds right now.

Removing the Whitelists

We filter the properties you pass to the components and sort them for style-related properties and actual HTML attributes. (primary vs. disabled on a button for example) We also keep a list of possible HTML nodes to create the styled.x shorthands. While that makes for a nice development experience, it introduces some unnecessary weight to the bundle.

Another long ongoing discussion and part of our future exploration is how we could create an API that lets us remove the whitelists while keeping the development experience intact. None of the ideas make us 100% happy yet, so weā€™ll keep exploring until we maybe find something that fits us well. (a current idea is adding namespacing to props in JSX)

If you want to help with any of this, have other ideas or find bugs please donā€™t be afraid to jump into the discussion! None of us bite, I promise.

Stay stylish. šŸ’™

--

--

Max Stoiber
šŸ’… styled-components

Technical cofounder of Spectrum.chat, creator of react-boilerplate, co-creator of šŸ’… styled-components and maintainer of KeystoneJS and micro-analytics.