Profile picture

Oscar Marion

A simple Tailwind & CRA setup

Mar 19, 2021 · 6 minutes read

In this guide, I’ll show you how to install Tailwind on a new Create React App (CRA) project. As a bonus, we’ll also add dark/light theme support.


In this guide, I’ll show you how to install Tailwind on a new Create React App (CRA) project. As a bonus, we’ll also add dark/light theme support.

Why Tailwind?

Tailwind is a popular utility-first CSS framework. It’s lightweight (1.4kB vs. Bootstrap’s 60.9kB), highly customizable, and works with CRA, Next.js, Vue, Svelte, and more. Unlike Bootstrap, it doesn’t come with pre-made components, which means more flexibility.

The Standard Way

Tailwind suggests using CRACO to customize CRA. While that works, it’s a bit much for simpler setups.

Our Approach

We’ll streamline things by using PostCSS and npm scripts to compile the CSS before running CRA.

A GitHub repository with the final code can be found here.

1. Install CRA

This command will create a starter project with Typescript support. You can of course remove the template flag and use regular Javascript.

npx create-react-app my-react-app --template typescript

2. Go to your app folder and install Tailwind, PostCSS and Autoprefixer like so:

npm i -D tailwindcss autoprefixer postcss-cli

3. Create a postcss.config.js file at the root of your project:

const tailwindcss = require("tailwindcss");
module.exports = {
    plugins: [tailwindcss("./tailwind.config.js"), require("autoprefixer")]
};

4. Create a Tailwind configuration file by running:

npx tailwind init

You should get a tailwind.config.js file that looks like this:

module.exports = {
    purge: [],
    darkMode: false, // or 'media' or 'class'
    theme: {
        extend: {}
    },
    variants: {
        extend: {}
    },
    plugins: []
};

5. Create a tailwind.css file in your project,

For instance under src/styles/tailwind.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

6. Add a css script to package.json

As well as a prestart and a prebuild:

{
  "scripts": {
    "css": "postcss ./src/styles/tailwind.css -o ./src/styles/tailwind.generated.css",
    "prestart": "npm run css",
    "prebuild": "npm run css"
  }
}

7. Import Tailwind css file

This way, we’re making Tailwind classes available to all our app’s components.

/*...*/
import "./styles/tailwind.generated.css";
/*...*/

Don’t forget to add tailwind.generated.css to .gitignore, as this file gets generated each time we run the start or build command. We didn’t use Tailwind classes yet, but now is a good time to run npm run startand check that everything works.

Make some changes

To check that the Tailwind classes get picked up, open ./src/App.ts and edit the Learn React link className property:

<a className="App-link bg-white rounded mt-2 px-2" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
    Learn React
</a>

You should see the changes in your browser pretty instantly and your Learn React link should now look like this:

Cleanup the production build

Say we want to deploy this app right now. When running npm run build , CRA is kind enough to provide some information on our bundle size. As you can see, this app is quite hefty:

The bundled css weighs far too much, and there’s a reason for it: it includes all the Tailwind css classes from the @base, @components and @utilities that we set earlier. We want this on our development environment, but certainly not in production — only the used classes should be included in the build.

To clean up the unused classes, we’ll need to change the purge configuration of tailwind.config.js file to:

module.exports = {
    purge: {
        mode: "all",
        content: ["./src/**/*.ts", "./src/**/*.tsx"]
    }
    // ...
};

If you didn’t go for the Typescript template, make sure to change your file extensions accordingly. Now, let’s make sure Tailwind knows we run the build in a production environment by installing cross-env:

npm i -D cross-env

And change our prebuild script to:

"prebuild": "cross-env NODE_ENV=production npm run css"

As you run the build script, the bundled css weight should decrease a lot. To test that, npm run build once again:

Finally, let’s make sure our Tailwind classes are still applied: serve the built project by running npx serve -s build. Learn React should still look like on the dev environment.

Bonus: setup a light / dark mode switcher

1. Use CSS variables

Let’s extract the colors that are currently set in the src/App.css classes and set them as css variables:

:root {
    --color-primary: #282c34;
    --color-secondary: #ffffff;
    --color-accent: #61dafb;
}

You can now assign them to Tailwind variables. Your entire configuration should look like this:

module.exports = {
    purge: {
        mode: "all",
        content: ["./src/**/*.ts", "./src/**/*.tsx"]
    },
    darkMode: false, // or 'media' or 'class'
    theme: {
        extend: {
            colors: {
                primary: "var(--color-primary)",
                secondary: "var(--color-secondary)",
                accent: "var(--color-accent)"
            }
        }
    },
    variants: {
        extend: {}
    },
    plugins: []
};

As you changed the Tailwind configuration, you’ll have to stop the dev server and launch npm run startagain.

In App.css, you can remove the background-color and color properties set for the .App-header class, as well as the entire .App-link class.

Going back to App.tsx, make some changes to get the same look as before:

  • Add bg-primary text-secondaryto the header className attribute
  • For the Learn React link, replace App-link class by text-accent and bg-white by bg-secondary.

2. Enable dark mode

Create the light CSS variables under a data attribute set on the root div in App.tsx. To do that, add this to App.css, below the CSS variables you set before:

[data-theme="light"] {
    --color-primary: #ffffff;
    --color-secondary: #282c34;
    --color-accent: #61dafb;
}

We can now switch between these themes. To keep things simple for this tutorial, we will handle the theme in the component’s local state with the useState hook. In the real world, you would obviously save user preference in a cookie or in localStorage. You could also base the initial theme on prefers-color-scheme.

Let’s set our localState:

const [theme, setTheme] = useState(“dark”)

Add the current theme to the App.tsx root div:

<div className=“App” data-theme={theme}>

Next, let’s create our handler:

import React, { MouseEvent, useState } from 'react';
// ...
function App() {
  const [theme, setTheme] = useState(“dark”)

  const handleSwitchTheme = (e: MouseEvent<HTMLAnchorElement>) => {
    e.preventDefault();
    setTheme(theme === "dark" ? "light" : "dark")
  }
  // ...
}

Time to add a switch theme button. We will use the Learn more link and change it to our needs:

Try hitting the Switch theme button. If this fancy light theme shows up, congratulations, you got everything working!