dcastil / tailwind-merge

Merge Tailwind CSS classes without style conflicts
https://npmjs.com/package/tailwind-merge
MIT License
4.63k stars 64 forks source link

background image and background color is merged making it impossible to apply both #488

Open crtl opened 2 weeks ago

crtl commented 2 weeks ago

Describe the bug

When using custom gradients you may want to apply both a gradient as background-image and a background-color. Currently tailwind-merge will remove "duplicate" properties based on their prefix even though they apply different styles.

To Reproduce

Having following custom gradient:

backgroundImage: {
  "my-gradient": "linear-gradient(0deg, var(--alpha-50, rgba(9, 9, 11, 0.50)) 0%, var(--alpha-50, rgba(9, 9, 11, 0.50)) 100%)"
}

Will generate a classname bg-my-gradient to apply the gradient.

// Im using cn helper from shadcn:
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

cn("bg-red bg-my-gradient"); // Returns bg-my-gradient, but should be bg-red bg-my-gradient

Environment

Additional context

As a workaround you can use media-query modifiers:

cn("sm:bg-red bg-my-gradient"); // Returns sm:bg-red bg-my-gradient
dcastil commented 1 week ago

Hey @crtl! 👋 Thanks for your patience!

tailwind-merge doesn't have access to the tailwind.config.js file and you need to configure it separately so it knows about the bg-my-gradient class.

Here is an example on how to configure tailwind-merge: https://github.com/dcastil/tailwind-merge/blob/v2.5.4/docs/recipes.md#adding-custom-scale-from-tailwind-config-to-tailwind-merge-config.

And here is the documentation on how the tailwind-merge configuration works: https://github.com/dcastil/tailwind-merge/blob/v2.5.4/docs/configuration.md#usage-with-custom-tailwind-config.


(For myself)

Related: https://github.com/dcastil/tailwind-merge/discussions/489, https://github.com/dcastil/tailwind-merge/issues/469, https://github.com/dcastil/tailwind-merge/issues/447, https://github.com/dcastil/tailwind-merge/issues/368, https://github.com/dcastil/tailwind-merge/discussions/322, https://github.com/dcastil/tailwind-merge/issues/321, https://github.com/dcastil/tailwind-merge/discussions/315, https://github.com/dcastil/tailwind-merge/issues/302, https://github.com/dcastil/tailwind-merge/issues/276, https://github.com/dcastil/tailwind-merge/discussions/275, https://github.com/dcastil/tailwind-merge/issues/274, https://github.com/dcastil/tailwind-merge/issues/250, https://github.com/dcastil/tailwind-merge/issues/207

LikeDreamwalker commented 4 days ago

To make a note: This issue basically because tailwind-merge can't visit tailwind config directly, so even if we have declare the new variable in the tailwind config, tailwind-merge can't understand what all these custom variables are. And by some reasons in the tailwind-merge these custom variables and original tailwind variables will always try to overwrite each other. And the solution is above, to use the extendTailwindMerge to create the new extended merge functions.

For the shadcn/ui scenario, you can try this:

import { clsx, type ClassValue } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";

const customTwMerge = extendTailwindMerge({
  extend: {
    classGroups: {
      // Add custom merging rules here
      "font-size": [
        "text-h1",
        "text-h2",
        "text-h3",
        // ... and so on
      ],
      "bg-color": [
        "bg-background",
        "bg-foreground",
        "bg-card",
      ],
      "text-color": [
        "text-background",
        "text-foreground",
        "text-card-foreground",
      ],
      "border-color": [
        "border-background",
        "border-foreground",
        "border-card",
      ],
      z: ["z-app-bar", "z-popup", "z-menu", "z-tooltip", "z-modal"],
    },
  },
});

export function cn(...inputs: ClassValue[]) {
  return customTwMerge(clsx(inputs));
}

It is a solution for me, and I am not sure if this will affect shadcn/ui because Copilot also send some shadcn/ui variable into it.

dcastil commented 4 days ago

And by some reasons in the tailwind-merge these custom variables and original tailwind variables will always try to overwrite each other.

tailwind-merge by default treats classes that could be a color class as a color class, e.g. bg-whatever. I did this because colors are changed very often in the config and this allows you to set up custom colors without the need to configure that in tailwind-merge.

The downside of course is that all unknown classes starting with bg- will be interpreted as a color class and therefore removed if another background-color class comes after it.

LikeDreamwalker commented 4 days ago

And by some reasons in the tailwind-merge these custom variables and original tailwind variables will always try to overwrite each other.

tailwind-merge by default treats classes that could be a color class as a color class, e.g. bg-whatever. I did this because colors are changed very often in the config and this allows you to set up custom colors without the need to configure that in tailwind-merge.

The downside of course is that all unknown classes starting with bg- will be interpreted as a color class and therefore removed if another background-color class comes after it.

I understand and that is just a note. I think maybe it will be a better practice to build the custom tailwind variables first, and then use it to generate the tailwind config and extended tailwind-merge function. Sadly that's not the shadcn/ui's style ❤