DEV Community

Cover image for An Efficient React + TailwindCSS + Styled Components Workflow
Derek Shanks
Derek Shanks

Posted on • Updated on

An Efficient React + TailwindCSS + Styled Components Workflow

Notice December 2nd 2020

With the release of Tailwind 2.0 much of this article is overkill for newly started projects. However, many companies do stay behind on versions to ensure consistency.

If you are staying behind on versions before Tailwind 2.0. Carry on. Otherwise, I’ll be posting a new workflow covering React with Tailwind 2.0.

The Motive

Many can appreciate the problems TailwindCSS attempts to solve, providing low opinions on how to style your application. It comes with a cost of long line of class selectors that tends to overtake the template logic of your application.

Another DEV.to author @ryanlanciaux wrote an article which you can find here Tailwind with React. This started my mad labratory experiments. I was motivated to leverage the fantastic tools TailwindCSS offers.

A CSS library that I have utilized before, Tachyons, has a similar utility class strategy as Tailwind. I implemented an alternative Styled Component concept to manage the utility classes provided by Tachyons.

Combining Ryan's process with my strategy for Tachyons. The two methods arrive at an efficient TailwindCSS workflow. It is setup intensive. Worth the effort for maintaining a clean Tailwind style strategy that will scale nicely as your project grows.

Note: If you just want to dive into the project without having to build from scratch. Scroll to the bottom of the article and run the Create React App template command that will scaffold the entire project and dependencies discussed in this article.

We need a Create React App

First, we need a fresh Create React App install.

npx create-react-app my-new-app

cd my-new-app

Deleting the standard boilerplate from Create React App

The instructions coming next will help you create the following folder tree. Initially you'll want to delete the React boilerplate. logo.svg, index.css, App.css and remove their imports from index.js and App.js.

src
│       ├── App.jsx
│       ├── App.test.js
│       ├── AppStyles.styles.tw.js
│       ├── README.md
│       ├── index.css
│       ├── index.js
│       ├── serviceWorker.js
│       ├── setupTests.js
│       ├── tailwind.config.js
│       └── tailwind.css
Enter fullscreen mode Exit fullscreen mode

We need to install the rest of the dependencies to put Tailwind together with Styled-Components.

npm i --save tailwindcss tailwind.macro@next styled-components babel-plugin-macros postcss-cli autoprefixer

Create tailwind.config.js

Once the packages have installed. We need to initialize tailwind.config.js. We can customize Tailwind properties specific to the needs of our projects. For this example, I have added some configuration to change Tailwinds default 'blue-gray' tint to a flatter grayscale.

npx tailwind init

// ./src/tailwind.config.js
module.exports = {
  theme: {
    fontFamily: {
      display: ["Helvetica", "sans-serif"],
      body: ["Helvetica-Neue", "sans-serif"]
    },
    extend: {
      colors: {
        gray: {
          "100": "#f5f5f5",
          "200": "#eeeeee",
          "300": "#e0e0e0",
          "400": "#bdbdbd",
          "500": "#9e9e9e",
          "600": "#757575",
          "700": "#616161",
          "800": "#424242",
          "900": "#212121"
        }
      }
    }
  },
  variants: {},
  plugins: []
};
Enter fullscreen mode Exit fullscreen mode

Note: You need to move tailwind.config.js to the .src folder. Create React App has ModuleScopePlugin which prevents Tailwind Macro and Babel Macro Plugin from calling the tailwind.config.js from root folder. There are ways around this, It wasn't a critical or urgent enough issue to add these additional steps just to put the config file back into root.

All other build formats that do not include the ModuleScopePlugin can serve tailwind.config.js from root folder as usual.

Create Tailwind.css

Create the following file tailwind.css in .src directory adding the base Tailwind variables that call the utilities from Tailwinds package.

//./src/tailwind.css

@tailwind base;

@tailwind components;

@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Create babel-plugin-macros.config.js

We need to address the tailwind-macro package to utilize Tailwinds config file. This allows us to use the tw macro, (we will create this very soon) and allows Babel to read all of Tailwinds utilities.

Create babel-plugin-macros.config.js in your root and copy the following settings into the file.

//./babel-plugin-macros.config.js

module.exports = {
  tailwind: {
    plugins: ["macros"],
    config: "./src/tailwind.config.js",
    format: "auto"
  }
};
Enter fullscreen mode Exit fullscreen mode

Create postcss.config.js

And... finally we need to direct PostCSS to use Tailwind and Autoprefixer during compile. We will leverage this setup in a moment in our package.json file.

Add this file to your root directory.
postcss.config.js and copy the following settings into the file.

//./postcss.config.js

module.exports = {
  plugins: [
    // ...
    require("tailwindcss")("./src/tailwind.config.js"),
    require("autoprefixer")
    // ...
  ]
};
Enter fullscreen mode Exit fullscreen mode

Update your package.json

Home stretch. Add the following scripts to your package.json This will call PostCSS Cli to build and watch while you're developing as create-react-app module reloading updates live. It will also automatically generate index.css for you.

// package.json
 "scripts": {
    "build:css": "postcss src/tailwind.css -o src/index.css",
    "watch:css": "postcss src/tailwind.css -o src/index.css",
    "start": "npm run watch:css & react-scripts start",
    "build": "npm run build:css react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
  }
Enter fullscreen mode Exit fullscreen mode

Phew! Still with me? Awesome.

Note: File naming conventions are a matter of personal preference. I use the .jsx and styles.tw.js extensions for better visual distinction in my editors. .jsx gets the React logo, the .styles.tw.js gets the standard JavaScript logo. It helps differentiate template from the styles. Feel free to name your files however you find most comfortable.

Create AppStyles.styles.tw.js

Let's create our styled component first in the .src directory: Let's just get things up and running making incremental changes to ensure each change works correctly.

// ./AppStyles.styles.tw.js

import styled from "styled-components";

export const AppStyles = styled.div.attrs({
  className: "w-full h-screen bg-gray-100 p-2"
})``;
Enter fullscreen mode Exit fullscreen mode

Create App.jsx

We begin creating our template, copy the following code into App.jsx. You'll notice, styling is a little off at the moment if you're running the app. We'll fix that shortly. We only have part of Tailwind working currently.

// ./src/App.jsx

import React from "react";
import "./index.css";
import AppStyles from "./AppStyles.styles.tw";

const App = () => {
  return (
    <AppStyles>
      <h1>Greetings Earthling</h1>
      <p>
        Welcome to your Create-React-App / TailwindCSS / Styled Components
        template
      </p>
      <h2>Resources / Documentation</h2>
      <ul>
        <li>
          <a href="https://reactjs.org/docs/create-a-new-react-app.html">
            ReactJS
          </a>
        </li>
        <li>
          <a href="https://tailwindcss.com/">TailwindCSS</a>
        </li>
        <li>
          <a href="https://styled-components.com/">Styled Components</a>
        </li>
      </ul>
    </AppStyles>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Adding 'tw' macro to AppStyles.styles.tw.js

BAM! Things are working nicely so far. Great job! Now, let's add the additional magic to extend the powerful capabilities of using Tailwind isolated behind Styled Components. We'll add the tw variable and import the tailwind.macrolibrary from NPM.

Update your AppStyles.styles.tw.js file with the new code written below.

// ./src/AppStyles.styles.tw.js

import styled from "styled-components";
import tw from "tailwind.macro";

const AppStyles = styled.div.attrs({
  className: "w-full h-screen flex flex-col items-center justify-center"
})`
  & {
    h1 {
      ${tw`font-sans text-6xl font-hairline text-6xl text-teal-500`}
    }
    p {
      ${tw`text-gray-700 text-lg`}
    }
    h2 {
      ${tw`text-2xl font-hairline mt-5 text-teal-500`}
    }
    ul {
      ${tw`inline-flex`}
    }
    li {
      ${tw`mr-5`}
    }
    a {
      ${tw`text-blue-500 hover:text-gray-500 hover:underline`}
    }
  }
`;
export default AppStyles;
Enter fullscreen mode Exit fullscreen mode

Reviewing what we have just added, this setup now allows you to nest your Tailwind classes like you would with Sass. You can run through the entire component template using, class selectors, id's and html tags. It will follow all items from the parent container, each child and / or siblings. Pretty awesome, Right?

If you have the app currently running in the background. You need to restart it in order for all of the config files to compile with Create React App.

On completion of all steps, your file tree should look as follows.

Your root directory:

./styled-tailwind
├── .gitignore
├── README.md
├── babel-plugin-macros.config.js
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── src
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

Your .src folder

./src
├── App.jsx
├── App.test.js
├── AppStyles.styles.tw.js
├── index.css
├── index.js
├── serviceWorker.js
├── setupTests.js
├── tailwind.config.js
└── tailwind.css
Enter fullscreen mode Exit fullscreen mode

Great Job & Final Thoughts

That was a lot of setup prep work. There are many options you have available for your design workflow at this point. You're not limited to just Tailwind CSS classes. In between the backticks, you can also write normal CSS as follows. It offers lot of design possibilities and excellent opportunities to experiment with complex animations using Tailwind styled objects.



const AppStyles = styled.div.attrs({
    className: "w-full h-screen flex flex-col items-center justify-center"
})`
    & {
      h1 {
        ${tw`font-sans text-6xl font-hairline text-6xl text-teal-500`}
        transform: scale(1);
        transition: all 0.3s ease-in-out;
     }
      h1:hover {
      transform: scale(2);
    }
}
`;


Enter fullscreen mode Exit fullscreen mode

A Create-React-App template.

You don't have to do any of this again. I have created a template that can be used with Create React App. Everything we have installed in this article, will be pre-installed and ready for your next creative spark.

npx create-react-app my-new-app --template styled-tailwind

cd my-new-app

npm run start

Voila! Greetings Earthling!

Discussion (20)

Collapse
benrogerson profile image
Ben Rogerson

Hey maybe take a look at twin.macro which is up to date with the latest Tailwind. There's no need to use attrs with Twin unless you want to add the 'group' className in there.

Collapse
elie222 profile image
Elie

Hate to be so negative about an article but this is taking things a step backwards. The end result is an awful developer experience

Collapse
dbshanks profile image
Derek Shanks Author

You weren’t hating on being so negative. You decided to go the negative superior route of bashing an idea. Rather than approach it as a learning experience as to why some devs might prefer this method.

I never write CSS in my logic. I don’t like class selector pollution - not all of Tailwinds utilities are available via @apply method. Everything from Tailwind is available in the macro and keeps it out of my logic.

I specialize in complex UI animation utilizing tools such as GSAP, AnimeJS and CSS animation with intense data wrangling. This awful setup saves me a lot of time in the long haul. Tailwind has some really fantastic time saving utilities.

Ever see a full GSAP, useRef and onClick handler being mapped from Axios? Throw some Tailwind classes in there. You want to talk about a bad overall developer experience?

Collapse
plashenkov profile image
Yuri Plashenkov

Hey Elie! Could you explain a bit more detailed? Would be nice to hear your opinion.

Collapse
elie222 profile image
Elie

Mixing TW

const AppStyles = styled.div.attrs({
    className: "w-full h-screen flex flex-col items-center justify-center"
})`
    & {
      h1 {
        ${tw`font-sans text-6xl font-hairline text-6xl text-teal-500`}
        transform: scale(1);
        transition: all 0.3s ease-in-out;
     }
      h1:hover {
      transform: scale(2);
    }
}
`;

Just seems overly complexing mixing all these styles.
Stick with Styled. Or go full out with TW. Not sure the benefit of this mix.

Thread Thread
plashenkov profile image
Yuri Plashenkov

Ah, okay, got it, thank you!

Collapse
tomyeoman profile image
Tom Yeoman • Edited

Awesome thanks for writing this up! I've been thinking of this exact setup having tried both routes separately.

The problem I had was the tailwind macro on its own some selectors were not working. So I ended up just going the standalone tailwindcss route and found this.

Some questions I had -

1-

Will this double setup not lead to a large CSS payload. You've basically got every tailwind selector twice. Once via @tailwind utilities and then a second time when you're using tailwind macro via tw`` is this not an issue?

2-

In the AppStyles example className is mixed with ${tw}, why not just use ${tw` entirely? From my understanding this would allow you to not require the @tailwind utilities import ( providing you do not have the same issue as me where some classes do not work )

3 -

When I was looking @ the tailwind macro code it looks like all the tailwind > CSS mappings are hand crafted in there in order to translate. I had worried this can fall out of date ( and I believe already is ) easily without some love have you noticed this?

Collapse
dbshanks profile image
Derek Shanks Author

Hi Tom!

Thanks for taking the time to read the crazy contraption I have put together.

tw does not have access to the entirety of Tailwinds classes like pseudo selectors and many others.

The styled-component attr function has access to all of Tailwinds classes. With Babel macro config file we’re updating the macro on where to find Tailwind.

The idea is to use the attrs function as a parent container where all of Tailwinds classes are available and tw as secondary nested using the most common of Tailwinds selectors.

Also with the Styled Components function you have the ability to provide standard CSS for things that Tailwind does not have in its codebase.

I have another article that I am writing to handle Tailwind compression using @fullhuman/purgecss

As far as I know the tw macro only creates classes to access macro version of Tailwind classes but only relies on the single Tailwind import and does not duplicate.

Collapse
pankajpatel profile image
Pankaj Patel

I came across this project as well which can be interesting to integrate and get started with StyledComponents and Tailwind

emortlock.github.io/tailwind-react...
github.com/emortlock/tailwind-reac...

Collapse
gregiven profile image
GregIven

Idk if anyone else was having trouble with exporting the AppStyles, maybe a typo, can't be sure because nobody else raised the issue.

But was getting an expected export default from the AppStyles import into the App.js. Adding curly braces around the AppStyles fixed the error. -> import { AppStyles } from ...

Collapse
rmcsharry profile image
Richard McSharry • Edited

Derek, thanks for this, seems awesome...have you applied it to a Gatsby project?

Although it seems that this page:
gatsbyjs.org/docs/tailwind-css/

shows how to the same thing using emotion styled components with tailwind - but after reading it, it seems to be not completely clear, so I found a detailed walkthrough for Gatsby:

nystudio107.com/blog/using-tailwin...

Collapse
ryanlanciaux profile image
Ryan Lanciaux

Really great post - cool to see how others are using React + Tailwind :D

Collapse
hood profile image
Andrea Cappuccio

Super useful, thank you!

Collapse
dbshanks profile image
Derek Shanks Author

Thanks Andrea. Glad you found it helpful and also for hanging in there till the end. Haha!

Collapse
guerrillacoder profile image
GuerrillaCoder

Why does tailwind 2 make this method redundant?

Collapse
urbankid profile image
urbankid

Great post, I'm just starting to integrate Tailwind with React in a Laravel project and this will help keep my templates from getting way to chaotic. Looking forward for more of your articles!

Collapse
dbshanks profile image
Derek Shanks Author

Thank you for the kind words.

I have more articles planned for Tailwind. I think Adam and Steve have created an amazing tool for UI development. I want to be a part of endorsing the Tailwind style system as it is phenomenal and create isolated style patterns that have minimal performance import.

Collapse
rkrdev profile image
R K

How to setup the same with nextjs?

Collapse
anandkashyap profile image
Anand Kashyap

First of all. Its a great post!!
I have one question - would purgecss work correctly with this setup?

Collapse
dbshanks profile image
Derek Shanks Author

Yes it does. I have had unpredictable results though unfortunately.
While this method has been efficient. Tailwind 2.0 is around the corner. There is far better support for its @apply method among many other great features that have been missing that would eliminate having to use this workflow.

With the expanded @apply method and Tailwinds automatic PurgeCSS inclusion. We're in for a really nice experience soon. 2.0 Beta is already out. Exciting.

This method I have written will still be applicable for those who need to work legacy code bases and projects.

Thank you for the support and sticking with the article!!