Art by https://twitter.com/ChrisMCarrasco

emotion

The Next Generation of CSS-in-JS

Update: This was written for the original version of emotion. Since this article was published we’ve increased performance and removed the babel plugin requirement along with a host of other changes. For an up to date look at emotion check out my article on version 8.

emotion is a high performance, lightweight css-in-js library. The core idea comes from Sunil Pai’s glam library and its philosophy is laid out here. The basic idea is simple. You shouldn’t have to sacrifice runtime performance for good developer experience when writing CSS. emotion minimizes the runtime cost of css-in-js dramatically by parsing your styles with babel and PostCSS. The core runtime is 2.3kb and with React support, 4kb. Total.

First Look

The api will be familiar if you know styled-components. We’ve also taken inspiration from glamorous and css-modules.

const imageBase = css`
width: 32px;
height: 32px;
border-radius: 50%;
`
const Avatar = styled.img`
composes: ${imageBase};
border: 2px solid ${p => p.theme.borderColor}

@media(min-width: 420px) {
width: 96px;
height: 96px;
}
`

Benchmarks

This benchmark pushes the libraries by changing a dynamic value in the style block very quickly.

vs. styled-components — emotion is over 25x faster when using highly dynamic prop values at a third of the size.

styled-components warns against using this pattern and instead recommends using inline styles for highly dynamic values. emotion has no such limitations.

vs. glamorous — emotion is up to 2.5x faster on rerender at half the size.

css

import { css } from 'emotion'

css is the ❤️ of emotion. Most of the api, including styled, are just wrappers around css.

const imageBase = css`
width: 32px;
height: 32px;
border-radius: 50%;
`

css is a tagged template literal that accepts a standard css text with a few additional features such as nesting, pseudo selectors, and media queries.

css returns a string class name that can be used on any element. For our imageBase style block above it would be something like css-imageBase-12345.

To aid with composition css accepts a special property in the style block called composes.

With composes we can use our imageBase from above to quickly create avatar styles.

const avatarStyle = css`
composes: ${imageBase};
border: 1px solid #7519E5
`

Internally emotion will append these css classes to the generated one. avatarStyle from above would generate a class name like css-imageBase-12345 css-avatarStyle-12345 This allows for some really powerful composition that really shines with styled.

css also accepts object styles 😏

const imageStyles = css({
width: 96,
height: 96
})

object styles are not auto-prefixed.

styled

import styled from 'emotion/react'

styled is a thin wrapper around css and the supports the same style text and expressions as it does.

styled is modeled almost exactly like styled-components styled function.

const Avatar = styled.img`
width: 32px;
height: 32px;
border-radius: 50%;
`

styled also works as a function call. The first argument can be any html tag or React component that accepts a className prop.

const BigAvatar = styled(Avatar)`
width: 96px;
height: 96px;
`

You can also use another styled component as a selector.

const Heading = styled.img`
font-family: serif;
`
const Header = styled.header`
display: flex;
${Heading} {
font-size: 48px;
align-self: flex-end;
}
`

Composes works too

const imageBase = css`
width: 32px;
height: 32px;
border-radius: 50%;
`
const Avatar = styled.img`
composes: ${imageBase};

@media(min-width: 420px) {
width: 96px;
height: 96px;
}
`

In styled, the value of a composes interpolation can be a function. It is called with the current props on render. It must return an className or object style.

const types = {
success: css`
background: green;
`,
error: css`
background: red;
`,
}
const Alert = styled.div`
composes: ${props => types[props.type]};
padding: 10px;
`

Remember css just returns a className so this works.

The true power of composes shines through when used with something like styled-system by Brent Jackson.

Theming

import { ThemeProvider } from 'emotion/react'

Theming is provided by the theming library. The api is laid out in detail here. It is both based on styled-components theming and heavily tested which made it a no brainer.

Whenever you provide a theme to ThemeProvider any styled component has access to those styles via props.theme. It does not matter how nested your component is inside ThemeProvider, you still have access to props.theme.

const theme = {
white: '#f8f9fa',
purple: '#8c81d8',
gold: '#ffd43b'
}
const Avatar = styled.img`
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid ${props => props.theme.gold}
`
<ThemeProvider theme={theme}>
<Avatar src={avatarUrl}>
hello world
</Avatar>
</ThemeProvider>

Extract Mode vs Inline Mode

The emotion babel plugin defaults to what we call “extract mode”. In this mode, we take all of your css that is defined in each file and extract it to [filename].emotion.css and automatically import it in the top of your js file. Dynamic values are handled with css variables. The only updates at runtime are just changes to css variables! The disadvantages of extract mode include no ie11 support due to css variables and not being able to extract critical css for server side rendering.

In “inline mode” emotion does something a bit different.

Inline mode uses the CSS Object Model (CSSOM) to manipulate css from javascript. This is how every other css-in-js library out there works. What sets emotion apart is that we can precompile all of your style rules. We don’t have to parse and auto-prefix your styles at runtime because we reduce your styles down to raw insertRule calls.

Inline mode’s biggest advantages are that it works on ie9 and above and it has proper server side rendering support with extractCritical. There is a great example of this over in the next.js repo


Under the hood

The following example is for inline mode. Extract mode works almost exactly the same.

This code

const H1 = styled.h1`
font-size: 48px;
color: ${props => props.color};
`

Is converted to the following when babel compiles it

const H1 = styled(
'h1',
['css-H1-duiy4a'], // generated class names
[props => props.color], // dynamic values
function createEmotionStyledRules (x0) {
return [`.css-H1-duiy4a { font-size:48px; color:${x0} }`]
}
)

Whenever the render method of H1 is called:

  1. styled goes through each dynamic value in your css block. As it iterates through this list, any value that is a function is called and given the current props.
  2. createEmotionStyledRules is called using the final values from step 1 as arguments. In our example here x0 would be the result of props => props.color.
  3. The result of this call is then inserted into emotions StyleSheet shim where it is aggressively cached.
  4. The generated class names are appended to the component’s className prop.

Take a look at the source of styled and css.

Bonus

Keyframes

Animations are supported with the keyframes function.

https://github.com/tkh44/emotion#keyframes

css prop

Any component within your application that accepts a className prop can now accept a css prop.

const flexCenter = css`
display: flex;
align-items: center;
justify-content: center;`

<div
css={`
composes: ${flexCenter};
width: 128px;
height: 128px;
background-color: #8c81d8;
border-radius: 4px;

img {
width: 96px;
height: 96px;
border-radius: 50%;
transition: all 400ms ease-in-out;

&:hover {
transform: scale(1.2);
}
}
`}
>

The prop behaves exactly like the css function. The resulting class name is appended to the elements className prop.


Go write some CSS.

I want to thank Sunil Pai for his guidance, patience, and for trusting in me to carry out his idea. 🙏

Huge thanks to Mitchell Hamilton because without him emotion just wouldn’t work as well. His contributions were invaluable in getting us where we we are now.

Thanks to all the contributors, issue reporters, and early testers!

emotion.sh — website

slack.emotion.sh — slack channel — come say hi! 👋