Art by

emotion

The Next Generation of CSS-in-JS

Kye Hohenberger
Jul 10, 2017 · 6 min read

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 . For an up to date look at emotion check out .

is a , lightweight css-in-js library. The core idea comes from Sunil Pai’s library and its philosophy is laid out . 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 . We’ve also taken inspiration from and .

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 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 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 by Brent Jackson.

Theming

import { ThemeProvider } from 'emotion/react'

Theming is provided by the . The api is laid out in . 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 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 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 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 calls.

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


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 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 and .

Bonus

Keyframes

Animations are supported with the keyframes function.

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 were invaluable in getting us where we we are now.

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

— website

— slack channel — come say hi! 👋

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade