DEV Community

Cover image for Tailwind CSS under the hood
Max Shen
Max Shen

Posted on • Updated on • Originally published at m4xshen.dev

Tailwind CSS under the hood

Tailwind CSS defines itself on its official site as:

A utility-first CSS framework packed with classes.

In this post I'll explain to you what does utility-first mean and how Tailwind CSS works under the hood.

Utility-first

Utility classes are easily understood, single purpose classes. For example, Tailwind provides utility classes such as:



.mx-auto {
  margin-left: auto;
  margin-right: auto;
}


Enter fullscreen mode Exit fullscreen mode


.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem;
}


Enter fullscreen mode Exit fullscreen mode

So when you want to horizontally center a paragraph with small font size, you can just use mx-auto text-sm as the class name of the element.

In this way, we can build complex components by combining different utility classes together.

3 main benefits of utility-first

Now we understand what is utility-first and let’s see the benefits of it:

1. Design with constraints

Using inline styles, every value is an arbitrary number. With utilities, you’re choosing styles from a predefined design system such as the default spacing scale:

default spacing scale

Take setting width for an example, you just use utility class such as w-48 or w-96, instead of specify the exact pixel you want. This makes it much easier to build visually consistent UIs.

2. CSS stops growing

Using a traditional approach, your CSS files get bigger every time you add a new UI component. With utilities, everything is reusable. This means the size of your CSS file stops growing after a certain point because the same utility classes are used again and again.

traditional css vs utility css

3. No need to invent class names

With traditional CSS, you might be adding class names like sidebar-inner-wrapper just to be able to style a <div>. With utilities, you just composing them to build a complex style you want.

How Tailwind CSS works under the hood

Tailwind CSS works by scanning all of files for class names at build time, then generating all of the corresponding CSS for those styles.

The paths to all of your content files is specified by content section of your tailwind.config.js:



/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./pages/**/*.{html,js}", "./components/**/*.{html,js}"],
  // ...
};


Enter fullscreen mode Exit fullscreen mode

Tailwind use regular expression to detect class names. Therefore when you want to use utility class conditionally like the following syntax doesn’t work:



<div className={`text-${error ? "red" : "green"}-600`}>Lorem Ipsum</div>


Enter fullscreen mode Exit fullscreen mode

This is because the regular expression doesn’t match any class name.

Instead, you should use:



<div className={error ? "text-red-600" : "text-green-600"}>Lorem Ipsum</div>


Enter fullscreen mode Exit fullscreen mode

In the second approach, Tailwind can match the string 'text-red-600' and 'text-green-600' and then generates the corresponding CSS.

This also explains why you can't use props to build class names. For example, this won't work:



function Button({ color, children }) {
  return (
    <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>
      {children}
    </button>
  );
}


Enter fullscreen mode Exit fullscreen mode

Instead, map props to complete class names that are statically detectable at build-time:



function Button({ color, children }) {
const colorVariants = {
blue: "bg-blue-600 hover:bg-blue-500",
red: "bg-red-600 hover:bg-red-500",
};

return <button className={</span><span class="p">${</span><span class="nx">colorVariants</span><span class="p">[</span><span class="nx">color</span><span class="p">]}</span><span class="s2"> ...}>{children}</button>;
}

Enter fullscreen mode Exit fullscreen mode




Wrap up

To sum up, we've learned that Tailwind CSS makes it easy to design neat and consistent websites. Knowing how it works also helps us understand why some things work well and others don't, guiding us to use Tailwind more effectively!

Top comments (22)

Collapse
 
karsten_biedermann profile image
Karsten Biedermann

Thank you for your excellent article 👍. I stopped using it at some point due to the poor code readability. In reality, in large projects, it often looks like this:

<a class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"></a>

There's a good reason why solutions like this now exist, offering readable classes for use: daisyui.com

Collapse
 
seandinan profile image
Sean Dinan • Edited

I agree that it becomes a bit cluttered looking as a big long string, but I stopped inlining classnames in JSX a while back and it pretty much resolved the problem. My usual component structure is something like:

- /ControlsBar
  - index.jsx
  - utils.js
  - ControlsBar.stories.js
Enter fullscreen mode Exit fullscreen mode

so that the component file is something like:

import { classes } from './utils';

export default function ControlsBar({onSave, onCancel}){
  return (
   <div className={classes.wrapper}>
    <button className={classes.cancel} onClick={onCancel}>Cancel</button>
    <button className={classes.save} onClick={onSave}>Save</button>
  </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

and the utils file has:

export const classes = {
  wrapper: 'flex justify-between items-center border-t border-grey-3',
  cancel: 'text-grey-4 bg-grey-1 hover:bg-grey-2',
  save: 'text-blue-4 bg-blue-1 hover:bg-blue-2',
}

/* ...any utility functions for the particular component */
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brense profile image
Rense Bakker

This is exactly what PandaCSS already does.

Thread Thread
 
seandinan profile image
Sean Dinan • Edited

I think it's a pretty common pattern. I made a package (tailwind-classlist) back in 2018ish that did something similar, although it's no longer maintained. Handled merging conflicting classes and everything as well.

Thread Thread
 
brense profile image
Rense Bakker

Nice!

Collapse
 
faiq157 profile image
Faiq Ahmad

NYC bro

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix • Edited

If you do not like how all classes are smashed together maybe spend some time grouping them together ?

Here is an example from one of the more complex styled component taken from shadcn Dialog:

  className={classMerge(
     "z-50 relative", // overlay
     "w-full", // layout
     "grid gap-4", // layout children
     "bg-background", // bg
     "p-6 shadow-lg sm:rounded-lg", // border
     "duration-200", // animation
      "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", // animation open
      "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", // animation closed
     className,
   )}
Enter fullscreen mode Exit fullscreen mode

I find the code above pretty readable, easy to summarize and skim through.
Yes, its a little bit of extra work to write it like this.
Yes, you have to create separate components for each used div -> a good practice in React/Svelte/Solid/Vue anyway.

Collapse
 
littleoddboy profile image
Amirhosein Kalari!

I find something intresting. I'm not sure about the date, but it was 2022 I think that TailwindCSS and Prettier (a VScode add-on) featured with a very good updates. While you're doing refactoring the structure of your code with this add-on, it will sort all of your classes. Check it out in the end of the first page of tailwindcss.com
Enjoy it!

Thread Thread
 
karsten_biedermann profile image
Karsten Biedermann

When I spend more time making something disorganized organized, it's often better to do it myself. I'm taking a more pragmatic approach: native CSS is now really good. To make something clear and maintainable, I don't need a framework, just a good method to do it.

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

I know of it, I tried it.
I did not like the auto ordering of the tailwind classes.

If they are components that I use everywhere (like buttons) I want to make them as readable as possible.
The approach in my code example solved that goal in the best possible way known to me.

Collapse
 
bjoentrepreneur profile image
Bjoern

Like it a log. classMerge is amazing. Thanks for sharing :)

Collapse
 
bjoentrepreneur profile image
Bjoern

I like Tailwind because there are so many libs that build up on it, so you can use a solid set of components that were built with Tailwind, e.g. HeadlessUI or TailwindUI, so you can get the groundwork done fast and then customize them the way you want, while still keeping the option to fully change components with the ease of use of the Tailwind framework.

Collapse
 
brense profile image
Rense Bakker

Headless UI doesn't even have 1% of the components that other component libs offer and tailwind ui is a paid solution.

Thread Thread
 
bjoentrepreneur profile image
Bjoern

That's true, and I don't mind paying for components if they are well-made and maintained. TailwindUI is a great team with nice support and I can use their components to bring my projects up to speed in almost no time :)

Thread Thread
 
brense profile image
Rense Bakker

I'm not a fan of paying for things that should be open source.

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

daysyui -> is horrible in accessability. Its okay to use it for purely ui/graphical/design stuff but not for interactive components.

The web is pretty much all about accessibility, so ignoring it defeats the purpose to do web development in the first place.

If you want to do accessibility right in React then one of the 2 libraries is the way to go

Svelte/Sollid/Vue have their own similar specialized component libraries

Collapse
 
karsten_biedermann profile image
Karsten Biedermann • Edited

I cannot assess the quality of DaisyUI in terms of accessibility, but my point was that Tailwind is very cluttered in the DOM for my taste. Perhaps it is also important to say from which perspective I decide whether Tailwind is suitable for me. I build less in the way of applications and more websites. For me, it was a nightmare to write pseudo-elements, states, and simple appearances in Tailwind. I find this comparison here by DaisyUI very apt 😂.

Image description

Image description

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

I would disagree on the "websites are more complex than web app in terms of layout" claim

Proof
twitter.com/masumparvej_/status/17...

-> Try doing something like this to look good and responsive on all screen devices

Thread Thread
 
karsten_biedermann profile image
Karsten Biedermann

True!

Collapse
 
m4xshen profile image
Max Shen • Edited

I personally love NextUI. It works well with Tailwind and also has good accessibility (built on top of React Aria).

Collapse
 
pengeszikra profile image
Peter Vivo

I like to use tw fare better than using any other CSS solution because the layout related solution so simple like grid gap-2 for vertical list. Also can solve dark/light skin change and responsive design at zero cost.

Collapse
 
brense profile image
Rense Bakker

Tailwind likes to pretend it's the only option with those benefits, but that's not really (really not) true. PandaCSS for example has the same benefits, but none of the downsides of having to learn a new utility class language.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.