Nguyen-Blog
About
Published on

Tailwind CSS in Practice: What I’ve Learned

I've had the opportunity to work with Tailwind CSS in a couple of projects, and I have to admit—it not only has it boosted my development speed, but it has also streamlined my CSS writing process in ways I didn't expect. In this post, I'm sharing my experience with Tailwind CSS, and key takeaways from using this powerful utility-first framework.

What is Tailwind CSS?

The official definition states: "Tailwind CSS is a utility-first CSS framework that provides pre-defined classes to style HTML elements directly in your markup."

In simpler terms, Tailwind CSS is essentially a way to apply inline styles—but instead of writing CSS directly in the style attribute, you use predefined utility classes via the class property. Each utility class in Tailwind represents a single CSS rule, which is why it feels similar to inline styles but structured in a more reusable and maintainable way.

Why use Tailwind CSS?

What are the benefits of this approach? Here are a few key advantages:

  • Speed: Once you’re familiar with Tailwind’s class names, styling elements becomes incredibly fast.
  • Selector flexibility: Traditional inline styles (style attribute) don’t support CSS selectors, but Tailwind enables selector-based styling.
  • No need for custom class names: Naming CSS classes can be tricky. With Tailwind, you skip thinking about creating descriptive class names that make sense to others, or even to yourself later on.

How Tailwind CSS Works

Tailwind treats source code files as plain text, meaning it doesn’t parse or tokenize the code. Instead, it simply scans the files for possible class names and generates the corresponding styles dynamically.

Tell Tailwind CLI to Generate Classes in Advance

Since Tailwind's CLI treats source code files as plain text and scans for class tokens, we can ensure certain classes are generated by explicitly placing them in our source code. A simple trick is to include them within comments, like this:

/*
 * This component also uses these classes: w-full rounded-full border-none w-8 h-8
 */
function Button(props) {
    return <button {...props}>{children}</button>
}

By doing this, Tailwind detects these classes during its scan and includes them in the final CSS output.

Another way to achieve the same result is by using Tailwind’s @source inline() directive. You can read more about this approach here.

Dynamically Setting Background Images

To dynamically control which background image is displayed, you can use CSS custom properties along with Tailwind’s class syntax. Here’s an improved approach:

const styling = { '--bg-image': "url('/path_to_image')" };

<div className="bg-[image:var(--bg-image)]" style={styling}></div>;

Creating Component Classes When Needed

Tailwind CSS eliminates the hassle of coming up with descriptive class names, but that doesn’t mean you should never create new CSS classes. When a set of utility classes consistently serves the same purpose—and you can assign them a meaningful name—it’s a strong indication that they should be extracted into a reusable component class.

For example, if I often style error boxes using this long utility string:

border-l-8 border-red rounded-md shadow w-full p-2 flex gap-3 items-stretch bg-white text-stone-600 duration-300 ease-in-out hover:opacity-50

Instead of repeating this set of classes throughout my code, I can define a reusable component class:

@layer components {
    .error-box {
        @apply border-l-8 border-red rounded-md shadow w-full p-2 flex gap-3 items-stretch bg-white text-stone-600 duration-300 ease-in-out hover:opacity-50;
    }
}

By doing this, I keep my code cleaner, more maintainable, and easier to read—all while leveraging Tailwind’s utility-based approach.

Setting Up Multiple Breakpoints

In my experience, relying on standard breakpoints like tablet and desktop isn’t always enough when building a truly responsive website. Different devices and screen sizes demand more granular control, and thankfully, Tailwind CSS makes it incredibly easy to add custom breakpoints.

Since Tailwind follows a mobile-first design approach, here are my suggestions:

Define as many breakpoints as needed to ensure a seamless experience across various screen sizes.

Use screen width values to name breakpoints instead of device types (e.g., tablet = 768px). This prevents confusion and makes it easier to manage breakpoints.

@theme {
    --breakpoint-at390: 390px;
    --breakpoint-at430: 430px;
    --breakpoint-at768: 768px;
    --breakpoint-at1024: 1024px;
    --breakpoint-at1208: 1208px;
}

Fluid Design in Tailwind CSS

While Tailwind CSS doesn’t include fluid scaling by default, it doesn’t stop us from implementing it when needed. A great way to generate fluid font size and spacing scales is by using tools like Utopia. Once designed, you can configure Tailwind to support these values with custom CSS variables, like this:

--text--2: clamp(0.6914rem, 0.6781rem + 0.0664vw, 0.7378rem);
--text--1: clamp(0.7778rem, 0.747rem + 0.1538vw, 0.8854rem);
--text-0: clamp(0.875rem, 0.8214rem + 0.2679vw, 1.0625rem);
--text-1: clamp(0.9844rem, 0.9013rem + 0.4152vw, 1.275rem);
--text-2: clamp(1.1074rem, 0.9867rem + 0.6037vw, 1.53rem);
--text-3: clamp(1.2458rem, 1.0772rem + 0.8431vw, 1.836rem);
--text-4: clamp(1.4016rem, 1.1725rem + 1.1452vw, 2.2032rem);

By integrating fluid scaling, you create a more adaptable design that responds dynamically to different screen sizes, improving accessibility and user experience.

Conclusion

Tailwind CSS has streamlined my development workflow, making styling faster and more intuitive. Its utility-first approach simplifies class management while offering flexibility for custom designs. Whether refining responsiveness or enhancing maintainability, Tailwind proves to be a powerful tool. If you haven’t tried it yet, I highly recommend exploring its potential!