Andrew Welch · Insights · #css #JavaScript #frontend #tailwind #gatsby

Published , updated · 5 min read ·


For more tools, technologies, and techniques, check out the devMode.fm podcast!

Using Tailwind CSS with Gatsby, React & Emotion Styled Components

Learn how to use the util­i­ty-first Tail­wind CSS with Emo­tion CSS-in-JS” Styled Com­po­nents in a Gats­by JS + React project.

Tail­wind CSS is a util­i­ty-first CSS frame­work that allows for rapid­ly build­ing cus­tom designs, and it is some­thing I’ve adopt­ed as a stan­dard part of my workflow.

If you want to hear the why’s and how’s of Tail­wind CSS, check out the Tail­wind CSS Util­i­ty-First CSS dev​Mode​.fm pod­cast episode. For the pur­pos­es of this arti­cle, we’ll just assume you’re all on-board the Tail­wind Express like I am.

Since I’m a fan of Tail­wind CSS, when I start­ed work­ing with Gats­by & React, I want­ed a way to bring Tail­wind with me. I end­ed up decid­ing to go with the Emo­tion CSS-in-JS approach, specif­i­cal­ly using Styled Com­po­nents.

Again, we’ll just assume you’re on-board with Emo­tion CSS-in-JS; if not, check out the CSS in JS, an Emo­tion­al Top­ic dev​Mode​.fm pod­cast episode.

This arti­cle dis­cuss­es how to make all of these tech­nolo­gies play nice togeth­er, and explores some of the fun things the result­ing stack enables you to do.

If you want a head start on this set­up, check out Paulo Elias’s gats­by-tail­wind-emo­tion-starter to get you going. Oth­er­wise, buck­le up and let’s dive right in!

Link Why are we doing this?

If you haven’t worked with CSS-in-JS or Styled Com­po­nents before, there are some nice advantages:

  • You can use full-blown JavaScript
  • CSS is scoped to just the com­po­nent, and does­n’t bleed out”
  • You auto­mat­i­cal­ly get just the CSS used on each page
  • You auto­mat­i­cal­ly get Crit­i­cal CSS
  • The end result is just CSS

You may or may not be con­vinced that CSS-in-JS is a good idea, but these are tan­gi­ble ben­e­fits that I’ve found, and thus my desire to use CSS-in-JS and Styled Components.

Adding Tail­wind CSS to the mix brings all of the won­der­ful ben­e­fits of a utilty-first CSS frame­work like Tail­wind CSS to our Styled Components.

So with that said, let’s get to it!

Link A Hybrid Approach

The excel­lent Gats­by doc­u­men­ta­tion has two approach­es list­ed for using Tail­wind CSS with Gatsby:

We’re actu­al­ly going to use a hybrid approach, using both the tailwind.macro Babel Macro and PostC­SS.

We’re going to use the tailwind.macro to trans­late Tail­wind CSS class­es to Emo­tion Styled Com­po­nents for us, gen­er­at­ing just the CSS selec­tors we actu­al­ly use.

Then we’ll use PostC­SS for build­ing the Tail­wind CSS base styles (and any base glob­al styles we want to use) that will be applied glob­al­ly to every page. This gives us things like the Nor­mal­ize CSS reset as a base.

This is the rea­son for the hybrid approach: if we just used the tailwind.macro, we would­n’t get any of the Tail­wind CSS base styles.

You could also use the gats­by-theme-tail­wind­c­ss Gats­by Theme as a way to scaf­fold things, but I think it makes sense to under­stand how all of the pieces fit togeth­er first.

So let’s get going by installing Tail­wind CSS:

# Using npm
npm install --save tailwindcss

# Using Yarn
yarn add tailwindcss

Next up, let’s get tailwind.macro set up!

Link Setting up tailwind.macro

The tailwind.macro is a Babel Macro that allows you to use Tail­wind CSS with any CSS-in-JS library. We’ve cho­sen to use Emo­tion, so let’s get it all installed:

# Using npm
npm install --save @emotion/core @emotion/styled gatsby-plugin-emotion tailwind.macro@next

# Using Yarn
yarn add @emotion/core @emotion/styled gatsby-plugin-emotion tailwind.macro@next

Then we need to add the gatsby-plugin-emotion to our gatsby-config.js (in the project root):

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-emotion`,
      options: {
        // Accepts all options defined by `babel-plugin-emotion` plugin.
      },
    },
  ],
}

Then we need to cre­ate a babel-plugin-macros-config.js to tell it that we want to use Emo­tion, and where our tailwind.config.js file lives:

module.exports = {
    tailwind: {
        styled: '@emotion/styled',
        config: './tailwind.config.js',
        format: 'auto',
    }
};

Final­ly, there’s a small issue we need to work around, which appears to be due to Tail­wind CSS’s use of reduce-css-calc start­ing with Tailwind ^1.1.0, which appar­ent­ly depends on it being run via Node.

We can work around this by adding the fol­low­ing to our gatsby-node.js file (in the project root):

exports.onCreateWebpackConfig = ({actions, getConfig}) => {
    // Hack due to Tailwind ^1.1.0 using `reduce-css-calc` which assumes node
    // https://github.com/bradlc/babel-plugin-tailwind-components/issues/39#issuecomment-526892633
    const config = getConfig();
    config.node = {
        fs: 'empty'
    };
};

Link Using tailwind.macro

Now that we’ve got tailwind.macro installed, let’s have a look at how we can use it. It works very sim­i­lar to Emo­tion Styled Com­po­nents:

import React from 'react';
import tw from 'tailwind.macro';

const PageContainer = tw.div`
    bg-gray-200 text-xl w-1/2
`;

const Layout = ({children}) => (
    <PageContainer>
        {children}
    </PageContainer>
);

export default Layout;

This will cre­ate a Styled Com­po­nent that is com­posed of the styles from the Tail­wind CSS class­es list­ed in the tem­plate lit­er­al. There’s a Github issue that shows a good exam­ple of how this works.

In devel­op­ment:

import tw from 'tailwind.macro'
let styles = tw`w-1/2`

// ↓↓↓↓↓↓↓↓

import _tailwind from './path/to/your/tailwind.js'
let styles = {
  width: _tailwind.widths['1/2']
}

In pro­duc­tion (NODE_ENV=production):

import tw from 'tailwind.macro'
let styles = tw`w-1/2`

// ↓↓↓↓↓↓↓↓

let styles = {
  width: '50%'
}

You can see how it direct­ly uses the JavaScript Tail­wind CSS con­fig to extract styles. The only real down­side to this approach is that it will not work with Tail­wind Plu­g­ins.

Build­ing on the ini­tial exam­ple above, we can mix and match Emo­tion Styled Com­po­nents cus­tom CSS with Tail­wind class­es easily:

import React from 'react';
import styled from '@emotion/styled';
import tw from 'tailwind.macro';

import background from '../../static/fabric_plaid@2x.png';

const PageContainer = styled.div`
    ${tw`
        bg-gray-200 text-xl w-1/2
    `}
    background-image: url(${background});
    padding: 10px;
`;

const Layout = ({children}) => (
    <PageContainer>
        {children}
    </PageContainer>
);

export default Layout;

So it actu­al­ly com­bines the CSS styles from the Tail­wind CSS class­es in the ${tw` tem­plate lit­er­al with the cus­tom styled com­po­nent CSS below it. So you can do any of the fun pat­terns you do with Emo­tion Styled Com­po­nents, too!

In either case, only the CSS that is actu­al­ly used on a page will be extract­ed (no need for PurgeC­SS), and the styles used by com­po­nents on a page will be inlined, appear­ing some­thing like this:

<style data-emotion="css">
    .css-14xcvvf-PageContainer {
        background-color:#edf2f7;
        font-size:1.25rem;
        width:50%;
        background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQBAMAAABykSv/AAAAG1BMVEXq6urr6+vp6eno6Ojn5+fs7Ozt7e3m5ubu7u7dMibkAAAf6ElEQVR4AdyX4W3jSgyEp4WvhWmBLaiFaWFbeC2k7AfNSoICHHC4vyISO8vlfOQAdGxL2PaMx);
        padding:10px;
    }
</style>

The hashed CSS class name ensures that the styles are scoped to just the com­po­nent they are applied to, and will not leak out and affect any­thing else.

Sweet!

Link Setting up PostCSS

If we just left things as-is, using only the tailwind.macro, every­thing would still work, but we would­n’t get the Tail­wind CSS base styles to do things like apply a Nor­mal­ize CSS style reset, and oth­er glob­al styles.

Because these base styles are super use­ful, we’ll con­fig­ure PostC­SS to gen­er­ate them for us. So let’s install the PostC­SS pack­ages we’re going to use:

# Using npm
npm install --save gatsby-plugin-postcss postcss-import postcss-preset-env stylelint

# Using Yarn
yarn add gatsby-plugin-postcss postcss-import postcss-preset-env stylelint

Then we need to add the gatsby-plugin-postcss to our gatsby-config.js (in the project root):

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-emotion`,
      options: {
        // Accepts all options defined by `babel-plugin-emotion` plugin.
      },
    },
    {
      resolve: `gatsby-plugin-postcss`,
      options: {
        // Accepts all options defined by `gatsby-plugin-postcss` plugin.
      },
    },
  ],
}

We’ll also need to cre­ate a postcss.config.js file (in the project root) to tell it what PostC­SS plu­g­ins we want to use (includ­ing tailwindcss):

module.exports = {
    plugins: [
        require('postcss-import')({
            plugins: [
                require('stylelint')
            ]
        }),
        require('tailwindcss')('./tailwind.config.js'),
        require('postcss-preset-env')({
            autoprefixer: { grid: true },
            features: {
                'nesting-rules': true
            },
            browsers: [
                '> 1%',
                'last 2 versions',
                'Firefox ESR',
            ]
        })
    ]
};

The postcss.config.js file is used by PostC­SS to con­fig­ure the plu­g­ins and set­tings that PostC­SS will use. The impor­tant bit here is that we’re doing a require('tailwindcss') to include Tail­wind CSS.

The oth­er PostC­SS plu­g­ins we’re includ­ing here are just things that I find use­ful. You can read more about them in-depth in the An Anno­tat­ed web­pack 4 Con­fig for Fron­tend Web Devel­op­ment article.

Then we need to cre­ate a file for the Tail­wind CSS base styles, as well as any glob­al styles we might want to add in src/utils/globals.css:

@tailwind base;

// Add any global styles here

Final­ly, we need to load these styles in the gatsby-browser.js (in the project root):

import "./src/utils/globals.css"

That’s it! When we do a build, it’ll now gen­er­ate the Tail­wind CSS base styles, and any of our glob­al styles, and include them on the page. Here’s a trun­cat­ed ver­sion of what that looks like:

<style data-href="/styles.bf4a6a428ae909ee6f5e.css">
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

html {
	line-height: 1.15;
	-webkit-text-size-adjust: 100%
}

body {
	margin: 0
}

main {
	display: block
}

/* Truncated here */
</style>

Link Wrapping Up

That’s all she wrote! I’ve found that being able to inter­twine the famil­iar Tail­wind CSS styles that I’m used to with Emo­tion Styled Com­po­nents had cre­at­ed a real­ly com­pelling way to work with CSS.

In the process of actu­al­ly using CSS-in-JS and Styled Com­po­nents, I found that many of my uncer­tain feel­ings about it dis­ap­peared. It turned my frown upside down.

Part of the rea­son is that all of this is just a real­ly sophis­ti­cat­ed way to gen­er­ate and scope CSS in a way that allows for an excel­lent devel­op­er experience.

And since we’re using Gats­by to ren­der every­thing out to sta­t­ic pages, it results in an excel­lent user expe­ri­ence too.

The fact that it ends up scop­ing the CSS to each com­po­nent, and auto­mat­i­cal­ly inlin­ing just the CSS that we use on each page is pret­ty fantastic.

It makes process­es like PurgeC­SS and Crit­i­cal CSS as described in the Imple­ment­ing Crit­i­cal CSS on your web­site arti­cle unnecessary.

Give it a whirl, and you might just sec­ond that emo­tion.