Building Reusable React Components Using Tailwind — Smashing Magazine

Created
Oct 4, 2021 6:11 PM
Tags
devcsstailwind
Type
Article

Tilo Mitra studied Engineering at the University of Waterloo. He is an Engineering Manager and a Software Engineer. He’s been writing in JavaScript … More about Tilo …

image

In this post, we’ll look at several different ways you can build reusable React components that leverage Tailwind under the hood while exposing a nice interface to other components. This will improve your code by moving from long lists of class names to semantic props that are easier to read and maintain.

You will need to have worked with React in order to get a good understanding of this post.

Tailwind is a very popular CSS framework that provides low-level utility classes to help developers build custom designs. It’s grown in popularity over the last few years because it solves two problems really well:

  1. Tailwind makes it easy to make iterative changes to HTML without digging through stylesheets to find matching CSS selectors.
  2. Tailwind has sane conventions and defaults. This makes it easy for people to get started without writing CSS from scratch.

Add the comprehensive documentation and it’s no surprise why Tailwind is so popular.

These methods will help you transform code that looks like this:

To code that looks like this:

The difference between both snippets is that in the first we made use of a standard HTML button tag, while the second used a <Button> component. The <Button> component had been built for reusability and is easier to read since it has better semantics. Instead of a long list of class names, it uses properties to set various attributes such as size, textColor, and bgColor.

Let’s get started.

image

Method 1: Controlling Classes With The Classnames Module

A simple way to adapt Tailwind into a React application is to embrace the class names and toggle them programmatically.

The classnames npm module makes it easy to toggle classes in React. To demonstrate how you may use this, let’s take a use case where you have <Button> components in your React application.

Let’s see how to separate Tailwind classes so people using this <Button> component can use React props such as size, textColor, and bgColor.

  1. Pass props such as bgColor and textColor directly into the class name string template.
  2. Use objects to programmatically switch class names (as we have done with the size prop)

In the example code below, we’ll take a look at both approaches.

In the code above, we define a Button component that takes the following props:

  • sizeDefines the size of the button and applies the Tailwind classes text-xs or text-xl
  • bgColorDefines the background color of the button and applies the Tailwind bg-* classes.
  • textColorDefines the text color of the button and applies the Tailwind text-* classes.
  • childrenAny subcomponents will be passed through here. It will usually contain the text within the <Button>.

By defining Button.jsx, we can now import it in and use React props instead of class names. This makes our code easier to read and reuse.

Using Class Names For Interactive Components

A Button is a very simple use-case. What about something more complicated? Well, you can take this further to make interactive components.

For example, let’s look at a dropdown that is made using Tailwind.

An interactive dropdown built using Tailwind and class name toggling.

For this example, we create the HTML component using Tailwind CSS classnames but we expose a React component that looks like this:

Looking at the code above, you’ll notice that we don’t have any Tailwind classes. They are all hidden inside the implementation code of <Dropdown/>. The user of this Dropdown component just has to provide a list of options and a click handler, onOptionSelect when an option is clicked.

Let’s see how this component can be built using Tailwind.

Removing some of the unrelated code, here’s the crux of the logic. You can view this Codepen for a complete example.

The dropdown is made interactive by selectively showing or hiding it using the .hidden and .block classes. Whenever the <button> is pressed, we fire the onClick handler that toggles the isActive state. If the button is active (isActive === true), we set the block class. Otherwise, we set the hidden class. These are both Tailwind classes for toggling display behavior.

In summary, the classnames module is a simple and effective way to programmatically control class names for Tailwind. It makes it easier to separate logic into React props, which makes your components easier to reuse. It works for simple and interactive components.

Method 2: Using Constants To Define A Design System

Another way of using Tailwind and React together is by using constants and mapping props to a specific constant. This is effective for building design systems. Let’s demonstrate with an example.

Start with a theme.js file where you list out your design system.

In this case, we have two sets of constants:

  • ButtonType defines how buttons are styled in our app.
  • ButtonSizes defines the sizes of buttons in our app.

Now, let’s write our <Button> component:

We use the ButtonType and ButtonSize constants to create a list of class names. This makes the interface of our <Button> much nicer. It lets us use size and type props instead of putting everything in a class name string.

Versus the prior approach:

If you need to redefine how buttons look in your application, just edit the theme.js file and all buttons in your app will automatically update. This can be easier than searching for class names in various components.

Method 3: Composing Utilities With @apply

A third way to improve the legibility of your React components is using CSS and the @apply pattern available in PostCSS to extract repeated classes. This pattern involves using stylesheets and post-processors.

Let’s demonstrate how this works through an example. Suppose you have a Button group that has a Primary and a Secondary Button.

A Button Group consisting of a primary and secondary button. (Large preview)

image

Using the @apply pattern, you can write this HTML as:

Which can then be adopted to React to become:

Here’s how you would create these BEM-style classnames such as .btn, .btn-primary, and others. Start by creating a button.css file:

The code above isn’t real CSS but it will get compiled by PostCSS. There’s a GitHub repository available here which shows how to setup PostCSS and Tailwind for a JavaScript project.

There’s also a short video that demonstrates how to set it up here.

Disadvantages Of Using @apply

The concept of extracting Tailwind utility classes into higher-level CSS classes seems like it makes sense, but it has some disadvantages which you should be aware of. Let’s highlight these with another example.

First, by extracting these class names out, we lose some information. For example, we need to be aware that .btn-primary has to be added to a component that already has .btn applied to it. Also, .btn-primary and .btn-secondary can’t be applied together. This information is not evident by just looking at the classes.

If this component was something more complicated, you would also need to understand the parent-child relationship between the classes. In a way, this is the problem that Tailwind was designed to solve, and by using @apply, we are bringing the problems back, in a different way.

Here’s a video where Adam Wathan — the creator of Tailwind — dives into the pros and cons of using @apply.

Summary

In this article, we looked at three ways that you can integrate Tailwind into a React application to build reusable components. These methods help you to build React components that have a cleaner interface using props.

  1. Use the classnames module to programmatically toggle classes.
  2. Define a constants file where you define a list of classes per component state.
  3. Use @apply to extract higher-level CSS classes.

If you have any questions, send me a message on Twitter at @tilomitra.

Recommended Reading on SmashingMag: