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 thee2e
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.