image of a man place a puzzle piece

Create an awesome CSS-in-JS Gatsby setup using Emotion and Tailwind CSS

Tailwind CSS, and CSS-in-JS are very popular in the Gatsby development world these days. I've long been a fan of Tailwind CSS and the productivity spike it provides. Styled Components appeal to me as a clean and effective way of using a CSS-in-JS style pattern. However, it was not evident to me how to take Tailwind and effectively use it like Styled Components at first. Along came a babel plugin, and the clouds parted revealing a bright new shiny world.

Why?

I'm not going to get into the details of the advantages/disavantages of a utility first CSS framework (like Tailwind), or of using CSS-in-JS. There are valid arguments for and against and much comes down to personal or team preferences. If you're here, I'm assuming that you have decided to give it a try. If you are unsure, you may want to do some reading. I've chosen this option as I feel it results in code that is easier to maintain, and I'm always trying to look out for the future devs (usually future-me) that will maintain my code.

Setup

I've going to start with the Gatsby Default Project to have a clean slate. There are Gatsby starters that have Tailwind built in, but I think there is value in knowing how to set it up yourself.

gatsby new gatsby-starter-tailwind https://github.com/gatsbyjs/gatsby-starter-default

cd gatsby-starter-tailwind

Now I will install the needed components. I will need Tailwind CSS, Emotion (my preferred version of Styled Components), and Twin.macro which is a babel macro that is the real secret sauce to making this work. (Note: I use npm for installs - if you use yarn, adjust things accordingly.)

npm install tailwindcss --save-dev

npm install --save twin.macro @emotion/core @emotion/styled gatsby-plugin-emotion

Next, I update the config files to complete our setup.

// gatsby-config.js

module.exports = {
  plugins: [`gatsby-plugin-emotion`],
}
// gatsby-browser.js
// Note: This imports the Tailwind base styles

import "tailwindcss/dist/base.css"

To configure twin.macro I can update my package.json or add a separate config. I prefer a separate config since the package.json is used and updated by so many other things. This file goes in the project root.

// babel-plugin-macros.config.js

module.exports = {
  twin: {
    config: './tailwind.config.js',
    preset: 'emotion',
    hasSuggestions: true,
    debug: false,
  },
}

Next I will add an empty tailwind.config.cs which will allow me to add custom Tailwind classes in the future. I need this file in place because the babel config references it. This file goes in the root directory of the project.

// tailwind.config.js
module.exports = {
  theme: {
    extend: {},
  },
}

Note: I don't need to setup the PurgeCSS part of Tailwind; using Styled Components means that only the CSS that is in scope to a component will be loaded (one of the big advantages to this setup).

Usage

Now that everything is installed, I will take things for a spin by making some changes to our pages.

To be able to use this in a page/component, I l need to add the following import:

import tw, { styled, css } from 'twin.macro'

Then I can use Tailwind in components by using tw and use styled and css to apply Styled Components. The nice thing is that I can mix this in alongside any normal CSS or other component logic.

To show how this works in the code, I'm going to do some simple style changes to the header component.

Here is the header.js file that comes with the Gatsby Default Starter:

// src/components/header.js

import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"

const Header = ({ siteTitle }) => (
  <header
    style={{
      background: `rebeccapurple`,
      marginBottom: `1.45rem`,
    }}
  >
    <div
      style={{
        margin: `0 auto`,
        maxWidth: 960,
        padding: `1.45rem 1.0875rem`,
      }}
    >
      <h1 style={{ margin: 0 }}>
        <Link
          to="/"
          style={{
            color: `white`,
            textDecoration: `none`,
          }}
        >
          {siteTitle}
        </Link>
      </h1>
    </div>
  </header>
)

Header.propTypes = {
  siteTitle: PropTypes.string,
}

Header.defaultProps = {
  siteTitle: ``,
}

export default Header

The first thing I need to do is add twin.macro to the imports:

import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
import tw, { styled, css } from 'twin.macro'

Now I'm going to change the embedded styles on the page using various possibilities.

1. The first bit of styling is on the <header> tag. I'm going to create a new component called HeaderRoot to replace this tag, and I will add the styles to that. I will use Tailwind styles to replace the existing and will change the background color to blue so we can see the change take place. Here is the what the new component looks like:

const HeaderRoot = styled.header([
  // tailwind only style
  tw`bg-color-blue-600 mb-6`,
])

2. The next style is set on the <div> tag one level down. I'll create a new component called HeaderContainer for this and I will use a css block this time to apply style. The css block allows you to add vanilla CSS, but you can also inject Tailwind styles. I changed the max-width to 1100 to see the change take effect.

const HeaderContainer = styled.div([
  // inject tailwind into css block
  css`
    ${tw`my-0 mx-auto`}
    max-width: 1100px;
    padding: 1.45rem 1.0875rem;
  `
])

3. The next style is set on the <h1> tag. Instead of creating a new component, I'm going to apply the styles inline, much like they are now, only I will use Tailwind. I'm not suggesting this as the right way to do things; my goal is to show you different options. Where and how you add your style code is up to you. To do this I change the style parameter to css and then add my style block. I added extra margin here to see the change take effect. Here's what the Header section of the code now looks like:

const Header = ({ siteTitle }) => (
  <HeaderRoot>
    <HeaderContainer>
      <h1 css={[tw`m-4`]}>
          ...
      </h1>
    </HeaderContainer>
  </HeaderRoot>
)

4. The last custom styling on the page is on the link. Since Link is a custom component instead of an element that is part of styled already, I can't call it directly with dot notation, but will need to need to alter the syntax slightly and pass the component in as a parameter. Therefore, for Link, I call styled(Link). For the styles on Link, I decided to mix Tailwind and vanilla CSS again, but I show how you can define them side by side instead of injecting the tw into the css block. Both ways work, but some may prefer the look of one or the other. I changed the link text color to black to see the effect here.

const HeaderLink = styled(Link)([
  // use tailwind and css block separately if you prefer
  tw`text-black`,
  css`
    text-decoration: none;
  `,
])

Here is the complete file after all my changes:

import { Link } from "gatsby"
import PropTypes, { nominalTypeHack } from "prop-types"
import React from "react"
import tw,  {  styled,  css  }  from  'twin.macro'

const HeaderRoot = styled.header([
  // tailwind only style
  tw`bg-color-blue-600 mb-6`,
])

const HeaderContainer = styled.div([
  // inject tailwind into css block
  css`
    ___CSS_0___
    max-width: 1100px;
    padding: 1.45rem 1.0875rem;
  `
])

const HeaderLink = styled(Link)([
  // use tailwind and css block separately if you prefer
  tw`text-black`,
  css`
    text-decoration: none;
  `,
])

const Header = ({ siteTitle }) => (
  <HeaderRoot>
    <HeaderContainer>
      <h1 css={[tw`m-4`]}>
        <HeaderLink
          to="/"
        >
          {siteTitle}
        </HeaderLink>
      </h1>
    </HeaderContainer>
  </HeaderRoot>
)

Header.propTypes = {
  siteTitle: PropTypes.string,
}

Header.defaultProps = {
  siteTitle: ``,
}

export default Header

There you have it

A simple and clean way to use Tailwind CSS AND Styled Components in your react code. This is certainly not the limit of how this can be used, but it gives you the foundation of how Tailwind CSS fits into the mix, which is important if you are reading documentation on Emotion and/or Styled Components which likely won't be referencing the Twin.macro.

Special Thanks

I'd be remiss is if didn't send out a huge thanks to Ben Rogerson for creating Twin.macro (and to Brad Cornes for creating it's predecessor) and to Adam Wathan for creating Tailwind CSS.