One-Off Styling Overrides With Tailwind-Merge

Tailwind-merge is perfect for overriding unique one-off styling cases without individually defining them within the component variant.

tailwindcss

Tailwind CSS provides a highly customizable low-level CSS framework that gives developers all the building blocks needed to build designs without any opinionated styles. However, there might be one-off cases where we need to override the default Tailwind CSS classes within a component. This situation is commonly encountered in reusable UI components.

Let's say we need larger padding for the below Button component in a unique case. If we add p-4 to the className, the CSS will render as border rounded px-2 py-1 p-4, and p-4 will be ignored.

The order of the classes in the className string doesn't matter and the only way to apply the p-4 style is to remove both px-2 and py-1.

const Button = ({ className, children, ...props }) => { const styles = `border rounded px-2 py-1 ${className || ''}`; return ( <button {...props} className={styles}> {children} </button> ); }; <Button className="p-4">Click</Button>; /* Won't work The CSS will be render: border rounded px-2 py-1 py-4 */

This is where Tailwind Merge comes to the rescue.

The twMerge utility function comes handy when you want to change the style of a component in a one-off case to overwrite an existing style.

import { twMerge } from 'tailwind-merge'; const Button = ({ className, children, ...props }) => { const styles = twMerge('border rounded px-2 py-1', className); return ( <button {...props} className={styles}> {children} </button> ); }; <Button className="p-4">Click</Button>; /* Works!! The CSS will be render: border rounded py-1 p-4 */

In general, there may be situations where you could use tailwind-merge, but it may not be the best choice. tailwind-merge is ideal for highly composed components, such as in design systems or UI component libraries. It allows for a broad range of styling use cases without the need to define one-off customization variants.

Consider tailwind-merge as an alternative solution, not the main tool for handling style variants.

Cons:

  • tailwind-merge increases the bundle size due to its reliance on a large configuration to identify conflicting classes.
  • It may grant too much freedom to devs, complicating maintenance, and refactoring over time.

Pros:

  • Accelerate development and iteration
  • Prevents premature abstractions
  • Adjust styles on a case-by-case basis, such as altering a button's background color, without the need to create a new variant.

If the disadvantages of tailwind-merge are too significant for your needs, here are some alternatives that might be a better fit.


Alternative: Using Tailwind's important modifier

Tailwind's important modifier can be helpful in unusual scenarios where you need to enhance specificity due to style conflicts. Be cautious when using important in your CSS, as it disrupts the natural cascading of stylesheets, making the maintenance and debugging of your code more challenging. Consider important as a last-resort option.

You can make any utility important by adding a ! character to the beginning:

<Button className="!py-4" />

Alternative: Using Props that toggle internal styles

This is the traditional method of styling components and is likely your default approach. For example, consider a variant prop that switches between primary and secondary button styles. The variant prop is used to toggle between the component's design variations, and you can utilize this pattern to define any number of styling scenarios for a component.

For example, if you need to make the button full width on a one-off, you can add an isFullWidth prop to the button component, which will internally toggle the w-full class.

However, this approach doesn't provide the same flexibility to override style properties, as the style overrides need to be explicitly defined.

const BUTTON_VARIANTS = { primary: 'bg-blue-500 text-white', secondary: 'bg-gray-200 text-black', }; function join(...args) { return args.filter(Boolean).join(' '); } function Button({ variant = 'primary', isFullWidth, ...props }) { const styles = join(BUTTON_VARIANTS[variant], isFullWidth && 'w-full'); return <button {...props} className={styles} />; } export const CodeSnippetsMain = () => { return ( <div> <Button isFullWidth>CLICK</Button> </div> ); };