Tip 48/72

Avoid Interpolations

Avoiding interpolations is crucial for maintaining clean, efficient, and error-free TailwindCSS projects.

Interpolations in class strings can create significant issues when working with TailwindCSS and Frontend Frameworks. They reduce maintainability, cause runtime problems, and often result in redundant or conflicting classes in the output HTML. This tip explains why you should avoid interpolations and the best alternatives to manage dynamic classes effectively.

What is Interpolation?

Interpolation refers to embedding variables or expressions directly into class strings. Below are some examples of interpolation in React components:

<div className={`rounded-xl border p-6 ${className}`}>
    {children}
</div>
export function Button(
    {color, children}:
    {color:string, children:React.ReactNode}
) {
    return (
        <button className={`h-9 px-4 rounded-xl text-white bg-${color}-600`}>
            {children}
        </button>
    )
}

While it might look clean initially, this practice has significant downsides.

Why interpolations are bad

1. CSS Purging Issues

TailwindCSS generates utility classes based on the files in your content array during the build process. Dynamic class interpolations like bg-${color}-500 cannot be detected because they are constructed at runtime. As a result, the associated CSS will not be included in the final build, leading to broken styles.

2. Redundant or Conflicting Classes

Interpolations can produce duplicate or conflicting classes in the rendered HTML. For example:

<div class="bg-blue-600 bg-indigo-500"></div>

In this case, bg-indigo-500 overrides bg-blue-600, but both classes are still included in the output. This bloats your HTML, decreases performance, and complicates debugging.

3. Hard to Debug

Since interpolations are evaluated at runtime, debugging style issues becomes much harder. You can’t easily trace where the unexpected class combinations are coming from.

4. Readability and Maintainability

Dynamic interpolations reduce the readability of your code, making it harder to understand, especially for teams or future developers maintaining the project.

5. Runtime Errors

If the interpolated values are invalid or undefined, they can lead to runtime errors or invalid class names in your HTML, breaking the intended design.

What to do instead

1. Use Merging Libraries (Tailwind Merge)

Tailwind Merge is a library that intelligently merges classes and resolves conflicts, ensuring clean and predictable output.

import { twMerge } from 'tailwind-merge';

export const Card = ({children, className}) => (
    <div className={twMerge("rounded-xl border p-6 bg-blue-600", className)}>
        {children}
    </div>
);

This ensures that conflicting classes (e.g., bg-blue-600 and bg-indigo-500) are resolved, and only the last applicable class remains in the final HTML.

<!-- Output -->
<div class="bg-indigo-500"></div>

2. Use Tailwind Variants or CVA

Tailwind Variants is a library for managing class-based variants. It includes Tailwind Merge internally, allowing you to define reusable, type-safe variants for components. This avoids interpolation issues while keeping your components modular and scalable.

import { tv, VariantProps } from 'tailwind-variants';

const button = tv({
    base: 'font-medium rounded-full active:opacity-80',
    variants: {
        color: {
            blue: 'bg-blue-500 text-white',
            purple: 'bg-purple-500 text-white',
            indigo: 'bg-indigo-500 text-white',
        },
        size: {
            sm: 'px-2 py-1 text-sm',
            md: 'px-4 py-2 text-base',
            lg: 'px-6 py-3 text-lg',
        },
    },
    defaultVariants: {
        color: 'blue',
        size: 'md',
    },
});

type ButtonVariants = VariantProps<typeof button>;

interface ButtonProps extends ButtonVariants {
    children: React.ReactNode;
    className?: string;
}

export const Button = ({ color, size, className, children }: ButtonProps) => {
    return (
        <button className={button({ color, size, className })}>
            {children}
        </button>
    );
};
<!-- Button with default variants -->
<button class="rounded-full text-white bg-blue-500 px-2 py-1 text-sm"></button>

Tailwind Variants ensures clean, conflict-free, and predictable classes.

3. Conditional Classes with clsx

Clsx is another library that conditionally combines class names based on logic. It works well when combined with TailwindCSS.

import clsx from "clsx";

const Button = ({ isPrimary, className }) => {
    const classes = clsx(
        "rounded-lg px-4 py-2",
        isPrimary ? "bg-blue-500 text-white" : "bg-gray-300 text-black",
        className
    );

    return <button className={classes}>Click Me</button>;
};

Key Takeaways

  • Avoid Interpolations: They lead to purging issues, bloated HTML, and hard-to-debug runtime errors.
  • Use Tailwind Merge: A library that intelligently resolves conflicting classes and prevents duplication.
  • Leverage Tailwind Variants: A powerful tool that includes Tailwind Merge and provides a structured approach to managing variants.
  • Clean HTML Output: Libraries like Tailwind Merge and Tailwind Variants ensure that only the final applicable classes appear in the rendered HTML, avoiding redundant utilities.

By avoiding interpolations and using these libraries, you’ll ensure your code remains clean, efficient, and maintainable.