Using Tailwind CSS with Gatsby, React & Emotion Styled Components
Learn how to use the utility-first Tailwind CSS with Emotion “CSS-in-JS” Styled Components in a Gatsby JS + React project.
Andrew Welch / nystudio107
Tailwind CSS is a utility-first CSS framework that allows for rapidly building custom designs, and it is something I’ve adopted as a standard part of my workflow.
If you want to hear the why’s and how’s of Tailwind CSS, check out the Tailwind CSS Utility-First CSS devMode.fm podcast episode. For the purposes of this article, we’ll just assume you’re all on-board the Tailwind Express like I am.
Since I’m a fan of Tailwind CSS, when I started working with Gatsby & React, I wanted a way to bring Tailwind with me. I ended up deciding to go with the Emotion CSS-in-JS approach, specifically using Styled Components.
Again, we’ll just assume you’re on-board with Emotion CSS-in-JS; if not, check out the CSS in JS, an Emotional Topic devMode.fm podcast episode.
This article discusses how to make all of these technologies play nice together, and explores some of the fun things the resulting stack enables you to do.
If you want a head start on this setup, check out Paulo Elias’s gatsby-tailwind-emotion-starter to get you going. Otherwise, buckle up and let’s dive right in!
Why are we doing this?
If you haven’t worked with CSS-in-JS or Styled Components before, there are some nice advantages:
- You can use full-blown JavaScript
- CSS is scoped to just the component, and doesn’t “bleed out”
- You automatically get just the CSS used on each page
- You automatically get Critical CSS
- The end result is just CSS
You may or may not be convinced that CSS-in-JS is a good idea, but these are tangible benefits that I’ve found, and thus my desire to use CSS-in-JS and Styled Components.
Adding Tailwind CSS to the mix brings all of the wonderful benefits of a utilty-first CSS framework like Tailwind CSS to our Styled Components.
So with that said, let’s get to it!
A Hybrid Approach
The excellent Gatsby documentation has two approaches listed for using Tailwind CSS with Gatsby:
We’re actually going to use a hybrid approach, using both the tailwind.macro Babel Macro and PostCSS.
We’re going to use the tailwind.macro to translate Tailwind CSS classes to Emotion Styled Components for us, generating just the CSS selectors we actually use.
Then we’ll use PostCSS for building the Tailwind CSS base styles (and any base global styles we want to use) that will be applied globally to every page. This gives us things like the Normalize CSS reset as a base.
This is the reason for the hybrid approach: if we just used the tailwind.macro, we wouldn’t get any of the Tailwind CSS base styles.
You could also use the gatsby-theme-tailwindcss Gatsby Theme as a way to scaffold things, but I think it makes sense to understand how all of the pieces fit together first.
So let’s get going by installing Tailwind CSS:
# Using npm
npm install --save tailwindcss
# Using Yarn
yarn add tailwindcss
Next up, let’s get tailwind.macro set up!
Setting up tailwind.macro
The tailwind.macro is a Babel Macro that allows you to use Tailwind CSS with any CSS-in-JS library. We’ve chosen to use Emotion, 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 create a babel-plugin-macros-config.js to tell it that we want to use Emotion, and where our tailwind.config.js file lives:
module.exports = {
tailwind: {
styled: '@emotion/styled',
config: './tailwind.config.js',
format: 'auto',
}
};
Finally, there’s a small issue we need to work around, which appears to be due to Tailwind CSS’s use of reduce-css-calc starting with Tailwind ^1.1.0, which apparently depends on it being run via Node.
We can work around this by adding the following 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'
};
};
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 similar to Emotion Styled Components:
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 create a Styled Component that is composed of the styles from the Tailwind CSS classes listed in the template literal. There’s a Github issue that shows a good example of how this works.
In development :
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 production (NODE_ENV=production):
import tw from 'tailwind.macro'
let styles = tw`w-1/2`
// ↓↓↓↓↓↓↓↓
let styles = {
width: '50%'
}
You can see how it directly uses the JavaScript Tailwind CSS config to extract styles. The only real downside to this approach is that it will not work with Tailwind Plugins.
Building on the initial example above, we can mix and match Emotion Styled Components custom CSS with Tailwind classes 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 actually combines the CSS styles from the Tailwind CSS classes in the ${tw` template literal with the custom styled component CSS below it. So you can do any of the fun patterns you do with Emotion Styled Components, too!
In either case, only the CSS that is actually used on a page will be extracted (no need for PurgeCSS), and the styles used by components on a page will be inlined, appearing something like this:
`
.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;
}
`
The hashed CSS class name ensures that the styles are scoped to just the component they are applied to, and will not leak out and affect anything else.
Sweet!
Setting up PostCSS
If we just left things as-is, using only the tailwind.macro, everything would still work, but we wouldn’t get the Tailwind CSS base styles to do things like apply a Normalize CSS style reset, and other global styles.
Because these base styles are super useful, we’ll configure PostCSS to generate them for us. So let’s install the PostCSS packages 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 create a postcss.config.js file (in the project root) to tell it what PostCSS plugins we want to use (including 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 PostCSS to configure the plugins and settings that PostCSS will use. The important bit here is that we’re doing a require('tailwindcss') to include Tailwind CSS.
The other PostCSS plugins we’re including here are just things that I find useful. You can read more about them in-depth in the An Annotated webpack 4 Config for Frontend Web Development article.
Then we need to create a file for the Tailwind CSS base styles, as well as any global styles we might want to add in src/utils/globals.css:
`
@tailwind base;
// Add any global styles here
`
Finally, 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 generate the Tailwind CSS base styles, and any of our global styles, and include them on the page. Here’s a truncated version of what that looks like:
`
/*! 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 */
`
Wrapping Up
That’s all she wrote! I’ve found that being able to intertwine the familiar Tailwind CSS styles that I’m used to with Emotion Styled Components had created a really compelling way to work with CSS.
In the process of actually using CSS-in-JS and Styled Components, I found that many of my uncertain feelings about it disappeared. It turned my frown upside down.
Part of the reason is that all of this is just a really sophisticated way to generate and scope CSS in a way that allows for an excellent developer experience.
And since we’re using Gatsby to render everything out to static pages, it results in an excellent user experience too.
The fact that it ends up scoping the CSS to each component, and automatically inlining just the CSS that we use on each page is pretty fantastic.
It makes processes like PurgeCSS and Critical CSS as described in the Implementing Critical CSS on your website article unnecessary.
Give it a whirl, and you might just second that emotion.
Further Reading
If you want to be notified about new articles, follow nystudio107 on Twitter.
Copyright ©2020 nystudio107. Designed by nystudio107
Top comments (0)