Announcing š styled-components v2: A smaller, faster drop-in upgrade with even more features
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 preprocess
option 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. š