programming

This article is not a comparison of Tailwind vs Styled-Components; instead, it serves as a migration guide.

Styled components to Tailwind migration guide

Some time ago in one of my client projects, we attempted a comprehensive redesign, essentially a rebranding effort. It presented an ideal opportunity to migrate to Tailwind given the suboptimal Web Core Vitals score attributed to our usage of Styled-Components.

Styled-Components can impact runtime performance because it requires loading additional JavaScript. This contributes to a slightly lower score in Lighthouse.

So, we opted for Tailwind to achieve two goals in a single iteration: the rebranding and migration to a more performant and developer-friendly technology, in our opinion.

It's Not Simple

Tailwind employs the Atomic CSS/Functional CSS convention to construct views using a large number of available small classes. These classes are later Purged, and unnecessary ones are removed from the built bundle.

This is a completely different approach from what is used in Styled-Components. So, let's say you have the following styles in Styled-Components, and you want to migrate them to Tailwind. The following changes need to be applied:

// From Styled-Components

const Button = styled.button`
  background: red;
  color: white;
  /* ...etc */
`;

// to Tailwind

const Button = () => {
  return <button className="bg-red-600 text-white"></button>;
};

Indeed, for a simple component like Button, the migration is straightforward. However, when it comes to similarly rewriting an entire app, it can be a daunting and time-consuming task.

Furthermore, if you install Tailwind in your project and include basic stylings, it might alter your application's appearance. This is because of the different default reset bases that Tailwind applies, potentially conflicting with your existing styles.

@tailwind base; // Reset styles
@tailwind utilities; // Utils like p-5
@tailwind components; // Components

Preparing What is Needed

To initiate the migration, you must first set up Tailwind. This process is not covered in this article, so I recommend referring to the Tailwind Documentation for detailed instructions.

First of all, forget about the @tailwind base directive, as it might impact your current application's appearance, even if it's written in Styled-Components. All you need are these two directives:

@tailwind utilities;
@tailwind components;

Migration Process

The next step is to start writing components using the utility classes generated by the Tailwind Framework.

<button className="bg-red-600 text-white p-5"></button>

After, rewriting some components it's good to have some kind of regression. To check how it works from a low-level perspective, you can write the following snapshot tests:

import React from 'react';
import { render } from '@testing-library/react';
import Button from './Button';

test('Button snapshot', () => {
  const { asFragment } = render(
    <Button onClick={() => console.log('Button clicked')}>Click me</Button>
  );
  expect(asFragment()).toMatchSnapshot();
});

Alternatively, you can approach it from a high-level perspective by writing e2e tests. We utilized the Gherkin convention in these tests:

describe(`Button component works when`, () => {
  const { Given } = Gherkin(BASE_COMMANDS);

  it(`interaction and hover effects are attached`, () => {
    Given(`System sets pictures folder`, `button`)
    .And(`System cleans local storage`)
    .When(`I click button`, 'Click Me')
    .Then(`I see hover effect`)
    .And(`System takes picture`);
  });
});

If you're curious about the used Gherkin convention and the syntax in the e2e test example, here is the Crafting Own Gherkin Interpreter article for you.

Alternatively, you can leverage platforms like Chromatic to automatically generate visual snapshots for each component documented in Storybook files. We employed a total of three approaches to ensure that every possible scenario for each use case is covered, and the generated HTML attributes are valid.

How We Boosted Up Migration?

We utilized the tailwind-styled-components library to expedite the generation of classes from the existing styled-components styles code. While it may not work in all cases, it significantly reduces the time spent on this process.

Last Improvements

After rewriting all the required components, there is still a missing part - the global style setup, which is typically added in your setup file. It may look like the following:

const GlobalStyle = createGlobalStyle`
    html {
        font-size: 62.5%;
        height: 100%;
    }

    body {
        height: 100%;
        background: ${(props) => props.theme.body.bg};
        color: ${(props) => props.theme.font.default.color};
    }

    ul, ol {
        list-style: none;
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6,
    p,
    figure,
    picture,
    ul,
    ol,
    body,
    html,
    blockquote {
        margin: 0;
        padding: 0;
    }
`

Just remove it, and add the last missing part, this simple line:

@tailwind base;

Then, run your tests again to check for any regressions!

Summary and Conclusions

As you saw, migration to another technology can be tricky and requires carefully selected steps. It needs to be designed and planned well before initiation.

Well-written unit tests, e2e tests, or using Storybook with Chromatic can be good choices when you are concerned about preventing regressions.

Author avatar
About Authorpolubis

👋 Hi there! My name is Adrian, and I've been programming for almost 7 years 💻. I love TDD, monorepo, AI, design patterns, architectural patterns, and all aspects related to creating modern and scalable solutions 🧠.