5 best practices for preventing chaos in Tailwind CSS—Martian Chronicles, Evil Martians’ team blog

By admin
If you’re interested in translating or adapting this post, please contact us

first
ORDINAL

.

Working with Tailwind CSS is pretty fast and easy (that’s why it’s received such wide recognition). You just paste a list of different classes in your HTML—and your interface immediately becomes attractive! But, as the application grows, the lists of classes grow. Then, one day you realize you can’t understand your code, you’re confused with the structure of the application and magic variables, and work becomes a struggle. This article is all about avoiding this scenario, sharing some best practices to ensure you stay aloft when using

Tailwind
PRODUCT

CSS.

We can prevent any headaches and resolve any problems (for the most part) by using

Tailwind
PRODUCT

accurately and wisely. But, there are

two
CARDINAL

requirements your project must met, and if it does not,

Tailwind
PRODUCT

can make your job very difficult instead.

Better dynamic themes in

Tailwind
PRODUCT

with OKLCH color magic Better dynamic themes in

Tailwind
PRODUCT

with OKLCH color magic Read also


First
ORDINAL

, you should have a design system in your project.

Tailwind
ORG

’s philosophy goes alongside the design system where designers and developers use consistent design tokens. Design tokens are atomic values (like colors, spacing, or typography scales) that define a design’s properties and that are reused throughout the project.

Let’s imagine that we have a standard button and some tabs that need to be the same color as that button:


.button
PERSON

{ background-color : oklch (

45%
PERCENT

0.2 270 ) ; } .tab { background-color : oklch (

45%
PERCENT

0.2 270 ) ; }

If we decide to change the color scheme of the project a little, we’ll need to find every instance of this color (which looks like a magic variable) and update them everywhere. This can be inconsistent and harder to maintain.

Design tokens help prevent these problems and ensure uniformity across

UI
ORG

elements.

Luckily, to implement design tokens, we only need to to define the tokens in tailwind.config.js :

module . exports = { theme : { colors : { primary :

‘oklch(45%
PERCENT


0.2 270
DATE

)’ } } }

After adding a new color with the name primary , we can use bg-primary for our background color or text-primary for the text color throughout the application:

< button class = " bg-primary " > Standard button </ button > < div class = " bg-primary " >

First
ORDINAL

tab </ div >

This way, when you want to change the color scheme in the project, you only need to replace the color in

one
CARDINAL

place: tailwind.config.js .

It’s better to avoid using

Tailwind
PRODUCT

if you haven’t considered a design system because you’ll have to write magic values in the class lists (like ‘p-[123px] mb-[11px] gap-[3px]’ ) or add a lot of new tokens ( 15px , 16px , 17px in the spacing config), and this will eventually bring a lot of mess to your code.

Having a consistent design system is good because it can help the development and design teams understand each other better.

For instance, within

Figma
ORG

, you can have a single shared source of truth for any values in your design system. But to make this system truly maintainable, you’ll need to introduce some conventions regarding token grouping and naming—which we’ll get into later in this article.


5
CARDINAL

signs your project needs a design system

5
CARDINAL

signs your project needs a design system Read also

This is the

second
ORDINAL

requirement your project needs to meet: you should already be using a component-based approach. The utility-

first
ORDINAL

approach can lead to quite cluttered and verbose HTML structures since

Tailwind
PRODUCT

classes apply directly to elements. This can mean the markup is harder to read and maintain, especially noticeable as your project grows.

The solution: actively using a component-based approach that encapsulates frequently used patterns (in our case, HTML elements appearing more than once) as separate components.

With this approach, we can keep things DRY. Moreover, we’ll still have a single source of truth for our

Tailwind
PRODUCT

styles, and we can easily update it together in

one
CARDINAL

place:

< button class = " bg-yellow-700 border-2 font-semibold border border-gray-300 text-green p-4 rounded " > Custom Button </ button > < CustomButton > Custom Button </ CustomButton >

If your development tool doesn’t allow you to split your code into components, it’s likely that the utility-

first
ORDINAL

approach of

Tailwind
ORG

will only make development harder, and you should probably look to other CSS frameworks-for example,

CSS Modules
ORG

.

And

one
CARDINAL

last thing regarding a component-based approach: avoid using the @apply directive:

.block { @apply bg-red-500 text-white p-4 rounded-lg active : bg-blue-700 active : text-yellow-300 hover : bg-blue-500 hover : text-yellow-300 ; }

Yes, using this directive, your code may look cleaner, but it throws away the key advantages of

Tailwind
ORG

: less mental overload when coming up with names for CSS classes, and the absence of regressions when changing styles (since with @apply they won’t be isolated within the component). Further, using it increases CSS bundle size.

The creators of

Tailwind
ORG

have also highlighted the importance of using the @apply directive with caution in the documentation.

If you met both requirements,

Tailwind CSS
PRODUCT

is likely a good framework option for you! Here are the most helpful practices for improving your long-term experience with it.


1
CARDINAL

. Use fewer utility classes when possible

When you build a list of utility classes for an HTML element, each new class adds additional complexity for the developers, and they’ll have to analyze and work with the code later (and this includes you, too). Of course, these lists are an essential and inherent feature of

Tailwind
PRODUCT

, but nevertheless, it’s better to write as little utility classes as possible.

Here are a few ways you can decrease the number of classes and get exactly the same results:

Instead of setting pt-4 pb-4 , you can just use py-4 . This also applies with the px , mx , and my properties.

, you can just use . This also applies with the , , and properties. Instead of flex flex-row justify-between , you can just use flex justify-between . This is because flex-row is the default value of the flex-direction property in

CSS
ORG

. In general, it can be valuable to remember some default values of other CSS properties ( flex-wrap , for example) to make it easier to spot use cases like this.

, you can just use . This is because is the default value of the property in

CSS
ORG

. In general, it can be valuable to remember some default values of other CSS properties ( , for example) to make it easier to spot use cases like this. Instead of writing a long class list like border border-dotted border-2 border-black border-

opacity-50
DATE

, you can set border-dotted border-2 border-black/50 and this will have the same effect: border-2 implies that border is set, and border-black/50 represents a shorthand for the

RGBA
ORG

format.

With a shorter list of classes, the next time you inspect the structure of your application, it’ll be much easier to analyze what’s going on.


2
CARDINAL

.

Group
ORG

design tokens and name them semantically

When working on a team, you probably agree that some clean coding practices (like the clear naming of variables) are really important for long-term development. That said, even if you’re working alone, it also can be worth setting some rules for code clarity, otherwise, you could get confused about your own project (for example, when returning after a break).

This approach is especially important while working with

Tailwind
PRODUCT

because reckless usage of such a large number of classes and design tokens can really bring confusion into your code.

As discussed above, using design tokens is a great practice, but just pasting them haphazardly can lead to chaos in your tailwind.config.js file.

To remedy this, group related tokens together in tailwind.config.js . This means that design tokens for breakpoints, colors, and so on, will be in specific areas and won’t mess with each other:

module . exports = { theme : { colors : { primary : ‘

oklch(75%
PERCENT


0.18 154
DATE

)’ , secondary : ‘oklch(40%

0.23 283
DATE

)’ , error : ‘oklch(54%

0.22 29
DATE

)’ } , spacing : { ‘sm’ : ‘4px’ , ‘md’ : ‘8px’ , ‘lg’ : ’12px’ } , screens : { ‘sm’ : ‘

640px
ORG

‘ , ‘md’ : ‘768px’ } , } , }

Here’s another important thing: keeping a single semantic naming convention for your tokens will make it easier to find the necessary tokens and expand the system as the application grows.

For example, to add a color for your error state, don’t just copy and paste the bright-red token from your

Figma
ORG

file into your

Tailwind
PRODUCT

configuration: put it into the colors section and give a more concise name like error . This will make the system much more consistent.


3
CARDINAL

. Keep class ordering

Here’s another clean coding convention: using a consistent order makes classes easier to read and understand. To illustrate, let’s take a look at some HTML elements with unsorted classes:

< div class = " p-2

w-1/2
PERSON

flex bg-black h-2 font-bold " >

First
ORDINAL

block with unsorted classes </ div > < div class = " italic font-mono bg-white p-4 h-2 w-3 flex " >

Second
ORDINAL

block with unsorted classes </ div >

In the blocks above, there are classes for different categories: dealing with the box model, display, typography, and so on—but they don’t have any sort of presentational order. We can apply a unified order to sort classes by categories:

< div class = " flex h-2

w-1/2
PERSON

bg-black p-2 font-bold " >

First
ORDINAL

block with sorted classes </ div > < div class = " flex h-2

w-3 bg-white
ORG

p-4 font-mono italic " >

Second
ORDINAL

block with sorted classes </ div >

Since maintaining class ordering manually requires a lot of time and attention, it’s much better to automate this work using the official

Prettier
ORG

plugin for

Tailwind
PRODUCT

CSS. To learn more about how to get started with it and the methodology of how the classes are sorted, we recommend reading this article.


4
CARDINAL

. Minimize build size

It’s important to keep bundle size as small as possible—heavy builds mean slow-loading pages, bad performance, and frustrated users.

Tailwind provides us with

thousands
CARDINAL

of utility classes, and it’s unlikely we’ll use all of them within a single project. So, how can we make sure that any unused styles won’t end up in our production build?

Size Limit: Make the Web lighter Size Limit: Make the Web lighter Read also

If you use

Tailwind
PRODUCT

version

3.0
CARDINAL

or above, the Just-in-Time (JIT) engine is enabled in your project by default—it ensures that CSS styles are generated as they are needed, and we won’t need to purge unused styles for production builds.

But if you’re using an older version of

Tailwind
PRODUCT

, you need to perform additional optimizations for your build—this can be done using PurgeCSS, a tool for removing unused CSS. This article explains how to do this in version

2.1
CARDINAL

and older. You can also enable the JIT mode manually in your tailwind.config.js file, like so:

module . exports = { mode : ‘jit’ , … }

This will make sure that we’re only including the necessary styles in our bundle.

There is another important thing to consider: always minify the final CSS for the production build. Minification removes all unnecessary characters (like whitespace, comments, and so on) and this will noticeably reduce file size.

Using the

Tailwind
PRODUCT

CLI, this can be done by setting –minify flag:


npx tailwindcss -o build.css –minify

PERSON

Or, if you’ve installed

Tailwind
PRODUCT

as a PostCSS plugin, you can use the

cssnano
ORG

tool for minification by adding it to your plugin list.

If we don’t consider optimization, the size of our CSS can end up really big (

more than several tens
CARDINAL

of kilobytes). Even in a small project with a few components with styles, there is a

30%+
CARDINAL

size difference after

minifying CSS
PERSON

and enabling JIT mode. To achieve this, you just need to add the minify flag and enable

jit mode
PERSON

, as described above.

If you want to learn more information about minification and compression for

Tailwind
PRODUCT

, check this section of documentation.

Tip: If you have design tokens in your project, make sure that they’re all actually being used. Unused design tokens confuse developers, make the configuration more complicated, and introduce unneeded messiness into your design system.


5
CARDINAL

. Prevent inconsistencies when overriding and extending styles

Imagine that we use a component with a custom button on our page:

< Button className = "bg-black" / >

And we have a

Button
PERSON

component that has some default style:

export const

Button
PERSON

= ( ) => { return < button className = "bg-white" > Test button < / button > }

In this case, the button will remain white–

Tailwind
ORG

doesn’t automatically override style and apply the black color, so we need to specify it in the

Button
PERSON

component:

export const

Button
PERSON

= ( { className = "bg-white" } ) => { return < button className = { className } > Test button < / button > }

There’s nothing inherently wrong about this aspect of

Tailwind
ORG

, but if we want to customize some appearance by overriding or extending a lot of styles, it can be cumbersome to pass classes via props each time.

Moreover, there is

one
CARDINAL

more drawback to this approach: accepting utilities via props can make it harder to ensure a consistent component view. This approach encourages using any utility combination for the same component across the app which can lead to a lack of visual consistency.

So, what can we do with it?

Instead of allowing any arbitrary utility classes to be passed via props, define a set of predefined variants:

const BUTTON_VARIANTS = { primary : "bg-blue-500 hover:bg-blue-600 text-white" , secondary : "bg-gray-500 hover:bg-

gray-600
PERSON

text-white" , danger : "bg-red-500 hover:bg-red-600 text-white" } ;

Then, change the

Button
PERSON

component so it can accept a variant prop. To make constructing className more convenient, you can use clsx:

export const

Button
PERSON

= ( { className , variant = BUTTON_VARIANTS . primary } ) => { return < button className = { clsx ( className , variant ) } > Test Button < / button > }

Tip: using clsx would be also especially handy if you need to construct classes conditionally.

After constructing className for the component, just use it, passing the desired variant:

< Button variant = "secondary" / >

Now, consistency is ensured, and despite the fact that we added a restriction on full customization, flexibility remains; we can add any new variant for the component or edit an existing one.

And the other benefit of this approach is that it allows for simpler maintenance: changes to utility classes can be made in

one
CARDINAL

place, and then propagated to every component of that variant in the app.

If for some reason you don’t want to use the sets of predefined variants, you can try the package tailwind-merge, which provides utility function twMerge to merge

Tailwind
PRODUCT

classes in JS without style conflicts–but it should be used carefully and only when necessary, since it is not the most lightweight and increases bundle size.

Summing up: how to use and how not to use

Tailwind

Tailwind
PRODUCT

is a powerful tool, but it’s important to use it while following some rules to prevent chaos from erupting in your project. Let’s sum up the principles that we listed above.


First
ORDINAL

of all, to get the most out of these practice, you should use

Tailwind
WORK_OF_ART

when you already have a design system and consistent design tokens and have opted for a component-based approach. Without breaking reusable elements into components, using

Tailwind
PRODUCT

will become painful sooner or later, leading to repetitive or verbose HTML structures.

Minimize the number of utility classes where possible Formulate code conventions within your team, for example, by grouping design tokens and naming them semantically Likewise, implement consistent class ordering and set up linters to ensure code cleanliness Minimize your bundle sizes: ensure you’ve included only the needed styles, and always minify the final CSS for production build When appropriate, try to define a set of predefined variants for your components; this will help avoid problems with inconsistencies and style overriding

By following these rules, you’ll be able to use

Tailwind
PRODUCT

for the long haul–with pleasure and without problems–giving your team the chance to revel in all the benefits it provides.

At Evil

Martians
NORP

, we transform growth-stage startups into

unicorns
ORG

, build developer tools, and create open source products. If you’re ready to engage warp drive, give us a shout!