speaktosteve logo

October 16, 2024

Using Tailwind CSS, CLSX and Prettier in a React/Next.js project

How to set up Tailwind CSS, CLSX and Prettier in a React/Next.js project with reference repository

[next.js, react, tailwind, clsx, prettier]

Table of Contents


Overview

clsx, the lightweight library generally used to simplify the use of conditional CSS class names in libraries such as React, is something I am pretty new to.

Like a lot of these small, convenience tools, its great to have in your toolchain, but sometimes the set up is a little time consuming. Thats where I hope this article can help.

I am using clsx with tailwind at the moment, and its a must that my team are using tailwind in a consistent way. I have always reached for tools such as eslint and prettier to ensure our codebase stays as consistent as possible and so I was keen to make sure that, if we use clsx, we don’t lose that consistency. This is where the prettier-plugin-tailwindcss plugin for prettier comes in.

This article and corresponding reference repo explains how we set up our toolchain

  • a basic Next.js app with tailwind
  • clsx
  • prettier
  • the prettier-plugin-tailwindcss plugin

Setup

The setup is simple, create a basic Next.js app -> https://nextjs.org/docs/getting-started/installation

npx create-next-app@latest

Choosing the defaults (TypeScript, ESLint, Tailwind CSS)

Setting up Next.js app

Create a simple component ‘heading’, running the following from the root of the new project folder:

mkdir -p src/app/components && cd $_ && touch heading.tsx

This should create a blank ‘heading.tsx’ file in a new src/app/components folder:

Setting up Next.js app

Lets create a simple component in that file:

export const Heading = () => {
    return <h1>Heading</h1>;
}

And reference the component from the src/app/page.tsx file:

import { Heading } from "./components/heading";

export default function Home() {
  return (
    <Heading />
  );
}

If you run the site:

npm run dev

And view the site in your browser (usually at http://localhost:3000), you should something like this:

Setting up Next.js app

Lets add a couple of Tailwind classes, make this pretty:

export const Heading = () => {
    return <h1 className="text-5xl mx-auto p-5">Heading</h1>;
}
Setting up Next.js app

Next, we are going to add a property to our heading component, so we can flip the colour of the text from a parent component

export const Heading = ({textColour}:{textColour: 'red'|'blue'}) => {
    return <h1 className={`text-5xl mx-auto p-5 ${textColour === 'red' ? 'text-red-700' : textColour === 'blue' ? 'text-blue-700' : ''}`}>Heading</h1>;
}
Side Note

An alternative approach is to use the logical && operator. This results, in this simple example, as something resembling our clsx example that you can see later in this article:

className={`text-5xl mx-auto p-5 ${textColour === 'red' && 'text-red-700'} 
  ${textColour === 'blue' && 'text-blue-700'}`}

The benefit is you dont have the annoying ' : '' OR statement with the empty string that we see in the ternary expression. However, this results in your generated markup being littered with useless ‘false’ statements in your class list (which wont happen if you use clsx):

Setting up Next.js app

Standard stuff so far right? We have a component that can take property that is used to conditionally set the text colour, between red and blue.

Lets update the page.tsx and see it in action:

import { Heading } from "./components/heading";

export default function Home() {
  return (
    <>
      <Heading textColour="red" />
      <Heading textColour="blue" />
    </>
  );
}

Beautiful right?

Setting up Next.js app

Adding clsx

Now the good bit, as you can see above, we are using a standard ternary expression to apply the desired tailwind class:

className={`text-5xl mx-auto p-5 ${textColour === 'red' ? 'text-red-700' : 
    textColour === 'blue' ? 'text-blue-700' : ''}`}

We’re going to leverage clsx to tidy this expression up a bit.

Lets install clsx

npm install --save clsx

Then update our ‘heading.tsx’ component:

import {clsx} from 'clsx';

export const Heading = ({textColour}:{textColour: 'red'|'blue'}) => {
    return <h1 className={clsx(
        'text-5xl mx-auto p-5',
        textColour === 'red' && 'text-red-700',
        textColour === 'blue' && 'text-blue-700'
      )}>Heading</h1>;
}

Nice, the component still renders as before, but its cleaner and (in my mind) easier to read. There is no annoying ' : '' empty OR bit and the resulting markup is clean (no ‘false’ strings being added):

Setting up Next.js app

Setting up the prettier-plugin-tailwindcss

If you followed the instructions in this article from the beginning then the npx create-next-app@latest command will have set up tailwind for you. If not, then you can add tailwind using the following commands to install and configure:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

To set up VS Code to integrate prettier with the built-in formatter, follow these instructions: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

Now, lets install prettier and the prettier plugin for tailwind:

npm install -D prettier prettier-plugin-tailwindcss

And create the pretter config file on the root of your project folder:

touch .prettierrc

I’m going to start with the basic configuration:

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}

And then add a reference to the tailwind plugin that we just installed:

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true,
  "plugins": ["prettier-plugin-tailwindcss"]
}

If you now save a file that contains tailwind class references you should see them being re-ordered as per the recommended class order.

Before:

<div class="pt-2 p-4">
  <!-- ... -->
</div>

After saving (or explicitly running the ‘format document’ command):

<div class="p-4 pt-2">
  <!-- ... -->
</div>

Configuring the prettier-plugin-tailwindcss to supporting clsx

In order for prettier to sort classes that are part of a clsx method call (and not in a className attribute) there is a further step.

Consider this improvement on the clsx example above:

import { clsx } from 'clsx'

export const Heading = ({ textColour }: { textColour: 'red' | 'blue' }) => {
    const classes = clsx(
        'mx-auto pt-5 p-5 text-5xl',
        textColour === 'red' && 'text-red-700',
        textColour === 'blue' && 'text-blue-700'
    )
    return <h1 className={classes}>Heading</h1>
}

The formatter wont pick up the tailwind classes that are part of the call to the cslx method in the ‘classes’ variable.

All we need to do is add the following line to our prettier config:

"tailwindFunctions": ["clsx"]

So the full file looks like this:

{
    "trailingComma": "es5",
    "tabWidth": 4,
    "semi": false,
    "singleQuote": true,
    "tailwindFunctions": ["clsx"],
    "plugins": ["prettier-plugin-tailwindcss"]
}

Now, after saving (or explicitly running the ‘format document’ command), you will see the classes have been sorted:

import { clsx } from 'clsx'

export const Heading = ({ textColour }: { textColour: 'red' | 'blue' }) => {
    const classes = clsx(
        'mx-auto p-5 pt-5 text-5xl',
        textColour === 'red' && 'text-red-700',
        textColour === 'blue' && 'text-blue-700'
    )
    return <h1 className={classes}>Heading</h1>
}

See Sorting classes in function calls part of the extension’s docs for a full reference.


You can find the complete solution here: https://github.com/speaktosteve/tailwind-clsx-prettier


References