Chris Biscardi

moon indicating dark mode
sun indicating light mode

Getting started with Emotion and Gatsby

Emotion is a library for authoring and composing CSS rulesets in a performant way. Here’s how to get started using it with Gatsby.

First, we’ll boot up a fresh gatsby site using the default starter and install the Gatsby plugin (as well as Emotion itself).

gatsby new my-emotion-site
yarn add gatsby-plugin-emotion @emotion/core

Then we need to add gatsby-plugin-emotion to our gatsby-config.js. We’ll add it at the top of our plugins list.

module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
plugins: [
`gatsby-plugin-emotion`,
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.app/offline
// 'gatsby-plugin-offline',
],
}

We can run yarn develop to run our new Gatsby site. The output will look something like this.

➜ yarn develop
yarn run v1.13.0
$ gatsby develop
success open and validate gatsby-configs — 0.009 s
success load plugins — 0.232 s
success onPreInit — 0.515 s
success delete html and css files from previous builds — 0.007 s
success initialize cache — 0.013 s
success copy gatsby files — 0.078 s
success onPreBootstrap — 0.008 s
success source and transform nodes — 0.064 s
success building schema — 0.333 s
success createPages — 0.001 s
success createPagesStatefully — 0.047 s
success onPreExtractQueries — 0.004 s
success update schema — 0.167 s
success extract queries from components — 0.137 s
success run graphql queries — 0.200 s — 8/8 40.32 queries/second
success write out page data — 0.004 s
success write out redirect data — 0.001 s
Generating image thumbnails [==============================] 6/6 0.2 secs 100%
info bootstrap finished - 5.413 s
⡀ onPostBootstrapdone generating icons for manifest
success onPostBootstrap — 0.212 s
DONE Compiled successfully in 4075ms 8:18:42 PM
You can now view gatsby-starter-default in the browser.
http://localhost:8000/
View GraphiQL, an in-browser IDE, to explore your site's data and schema
http://localhost:8000/___graphql
Note that the development build is not optimized.
To create a production build, use gatsby build
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.
>

Converting to Emotion

The default starter comes with no external styling implementation. There are no CSS files and no other libraries to implement styling. If we look at our index page at src/pages/index.js, we see the default starter uses inline styles.

import React from 'react'
import { Link } from 'gatsby'
import Layout from '../components/layout'
import Image from '../components/image'
import SEO from '../components/seo'
const IndexPage = () => (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
export default IndexPage

The result of this page (specifically the div containing inline styles and the image) is more complex than you might think because the image is processed into many different resolutions automatically by Gatsby. We’ll ignore most of the output and focus on the root div with the inline styles. We can see that they compile to a style attribute.

<div style="max-width: 300px; margin-bottom: 1.45rem;">
<div
class=" gatsby-image-wrapper"
style="position: relative; overflow: hidden;"
>
<div style="width: 100%; padding-bottom: 100%;"></div>
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAACYklEQVQ4y42Uy28SQRjA+dM8efDmwYN6qF6qiSY+Y/WgQRMibY00TaWNBSRSCraYQtHl/bR0KyxQWCgWWAqU8izl/Sq7rLNsRHlVJpvJtzPfb77nDIOcZHSoqZSrp4+KtXIziubaLRysMCZiCYqOoVnhjNEi8RcztdxxeTzc6VBfT+5O2Vhpb+vw4wMdZ0ppWvP9xzLeJoDNThf2W+Nz1+XzNxQubSToSKKW+BDc+WOnkshhSVgeCiGpViZMEg1oxc26Knt+ae3bEtJTZwzE1kXLccG0+sOOlrcvZXvsczPkITfsa20vwIKnhsh+BnjUarT74Gb13CY7KBVJMv3z4N1NszQYsMWM62HNrCis/GxXn0iYls23uz5LPBcv0bH8hUH2XRoM85ySXv7JBtO87jMIvWq+H5GoYIHCLA1ZxD6Qap3Ak8IKfW7TJ50lK6uP9E6RgndHaODtCJ6Z5RyHfnE7j6gRbcKlCYNSt+rtETHTpUGgEP8FYmdNqd/Mo7aiVWTfuH2L9xASvfxxlqr01EYkrJszvNkgW9bH0OuFr+99m+y9IOeyU6zIp/Hubp/yMEztlzFPwOhdvq+nIoS1JNn4t2sugCmVsDvPe2KKolnZLCxhOcAKQRDDXTQaVi46lqYhIBwHTrl3oWqhMRDtaJge37lOBMKo4tfbqhVX0J7snTsWps8uZWuoOQY6CcjpSIF55UvmqNgr5wUwtV1IVdnXtnSfPEB2qjDNqnvczRl0m+j6Jn5lXb6nAQJqinmN0ZEBj03YLzghY8PnTRz80o/GRJZpOLCb0PM9BN7pvUEjx28V00WUg9jIVwAAAABJRU5ErkJggg=="
alt=""
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 0; transition: opacity 0.5s ease 0.5s;"
/><picture
><source
srcset="
/static/6d91c86c0fde632ba4cd01062fd9ccfa/0552a/gatsby-astronaut.png 75w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/fc3be/gatsby-astronaut.png 150w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png 300w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/24f49/gatsby-astronaut.png 450w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/5497d/gatsby-astronaut.png 600w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/483e1/gatsby-astronaut.png 800w
"
sizes="(max-width: 300px) 100vw, 300px"/>
<img
alt=""
src="/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png"
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 1; transition: opacity 0.5s ease 0s;"/></picture
><noscript
><picture
><source
srcset="
/static/6d91c86c0fde632ba4cd01062fd9ccfa/0552a/gatsby-astronaut.png 75w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/fc3be/gatsby-astronaut.png 150w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png 300w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/24f49/gatsby-astronaut.png 450w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/5497d/gatsby-astronaut.png 600w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/483e1/gatsby-astronaut.png 800w
"
sizes="(max-width: 300px) 100vw, 300px"/>
<img
src="/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png"
alt=""
style="position:absolute;top:0;left:0;transition:opacity 0.5s;transition-delay:0.5s;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture
></noscript>
</div>
</div>

Most people want to keep inline styles to a minimum unless they’re performing an intensive animation on one of the values, so let’s convert this to use Emotion. The only change we need to make in this file is to swap styles for css.

import React from 'react'
import { Link } from 'gatsby'
import Layout from '../components/layout'
import Image from '../components/image'
import SEO from '../components/seo'
const IndexPage = () => (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div css={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
export default IndexPage

Yeah, that’s it. Let’s take a look at how it renders again.

<div class="css-ehkvu9-IndexPage">
<div
class=" gatsby-image-wrapper"
style="position: relative; overflow: hidden;"
>
<div style="width: 100%; padding-bottom: 100%;"></div>
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAACYklEQVQ4y42Uy28SQRjA+dM8efDmwYN6qF6qiSY+Y/WgQRMibY00TaWNBSRSCraYQtHl/bR0KyxQWCgWWAqU8izl/Sq7rLNsRHlVJpvJtzPfb77nDIOcZHSoqZSrp4+KtXIziubaLRysMCZiCYqOoVnhjNEi8RcztdxxeTzc6VBfT+5O2Vhpb+vw4wMdZ0ppWvP9xzLeJoDNThf2W+Nz1+XzNxQubSToSKKW+BDc+WOnkshhSVgeCiGpViZMEg1oxc26Knt+ae3bEtJTZwzE1kXLccG0+sOOlrcvZXvsczPkITfsa20vwIKnhsh+BnjUarT74Gb13CY7KBVJMv3z4N1NszQYsMWM62HNrCis/GxXn0iYls23uz5LPBcv0bH8hUH2XRoM85ySXv7JBtO87jMIvWq+H5GoYIHCLA1ZxD6Qap3Ak8IKfW7TJ50lK6uP9E6RgndHaODtCJ6Z5RyHfnE7j6gRbcKlCYNSt+rtETHTpUGgEP8FYmdNqd/Mo7aiVWTfuH2L9xASvfxxlqr01EYkrJszvNkgW9bH0OuFr+99m+y9IOeyU6zIp/Hubp/yMEztlzFPwOhdvq+nIoS1JNn4t2sugCmVsDvPe2KKolnZLCxhOcAKQRDDXTQaVi46lqYhIBwHTrl3oWqhMRDtaJge37lOBMKo4tfbqhVX0J7snTsWps8uZWuoOQY6CcjpSIF55UvmqNgr5wUwtV1IVdnXtnSfPEB2qjDNqnvczRl0m+j6Jn5lXb6nAQJqinmN0ZEBj03YLzghY8PnTRz80o/GRJZpOLCb0PM9BN7pvUEjx28V00WUg9jIVwAAAABJRU5ErkJggg=="
alt=""
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 0; transition: opacity 0.5s ease 0.5s;"
/><picture
><source
srcset="
/static/6d91c86c0fde632ba4cd01062fd9ccfa/0552a/gatsby-astronaut.png 75w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/fc3be/gatsby-astronaut.png 150w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png 300w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/24f49/gatsby-astronaut.png 450w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/5497d/gatsby-astronaut.png 600w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/483e1/gatsby-astronaut.png 800w
"
sizes="(max-width: 300px) 100vw, 300px"/>
<img
alt=""
src="/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png"
naptha_cursor="text"
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 1; transition: opacity 0.5s ease 0s;"/></picture
><noscript
><picture
><source
srcset="
/static/6d91c86c0fde632ba4cd01062fd9ccfa/0552a/gatsby-astronaut.png 75w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/fc3be/gatsby-astronaut.png 150w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png 300w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/24f49/gatsby-astronaut.png 450w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/5497d/gatsby-astronaut.png 600w,
/static/6d91c86c0fde632ba4cd01062fd9ccfa/483e1/gatsby-astronaut.png 800w
"
sizes="(max-width: 300px) 100vw, 300px"/>
<img
src="/static/6d91c86c0fde632ba4cd01062fd9ccfa/045aa/gatsby-astronaut.png"
alt=""
style="position:absolute;top:0;left:0;transition:opacity 0.5s;transition-delay:0.5s;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture
></noscript>
</div>
</div>

This time we can see that the containing div has a classname on it instead of inline styles… but where are the styles? Well in <head> there’s a new <style> tag that includes our styles and a source map (this is development mode after all).

<style data-emotion="css">
.css-ehkvu9-IndexPage {
max-width: 300px;
margin-bottom: 1.45rem;
}
</style>

Fin

This is how Emotion supports the full breadth of CSS which using a syntax that looks like inline styles. It uses built-in browser APIs (the CSSOM APIs) to insert styles in a performant way. The syntax we used to replace the style prop is know as the “CSS Prop”, named for the css prop we used on our React components. In our next posts, we’ll explore more of the Emotion API, composition of styles, theming, and how to use both the styled and css style APIs.