javascriptreactjstailwind-css

Tailwind custom classes overriding using class-variance-authority


I defined the following variables on the global.css file

@layer base {
  :root {
    --light-white: rgb(255, 255, 224);
    --light-red: rgb(241, 2, 4);
  }
}

the following classes on the tailwind.config.ts file

const config: Config = {
  content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
  theme: {
    extend: {
      colors: {
        "light-white": "var(--light-white)",
        "light-red": "var(--light-red)",
      },
      borderWidth: {
        lg: "50px",
        "lg-long": "85px",
      },
    },
  },
  plugins: [],
};

and the following variants for a div component

import { cva } from "class-variance-authority";

const customVariants = cva("size-0", {
  variants: {
    size: {
      lg: `border-t-lg
           border-l-lg-long
           border-b-lg`,
    },
    color: {
      red: `border-t-white
           border-l-light-red
           border-b-white`,
    },
  },
});
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
<div className={cn(customVariants({ color, size }))} />

When applying the variants to my component, the color classes override the size classes

Output:

<div class="size-0 border-t-white border-l-light-red border-b-white"></div>

If I swap the color and size definition positions, the size classes override the color classes

const customVariants = cva("size-0", {
  variants: {
    color: {
      red: `border-t-white
           border-l-light-red
           border-b-white`,
    },
    size: {
      lg: `border-t-lg
           border-l-lg-long
           border-b-lg`,
    },
  },
});

Output:

<div class="size-0 border-t-lg border-l-lg-long border-b-lg"></div>

It works if I apply the tailwind classes directly to the div

<div className="size-0 border-t-lg border-l-lg-long border-b-lg border-t-white border-l-light-red border-b-white" />

It also works if I remove the custom borderWidth classes (border-t-lg, border-l-lg-long and border-b-lg) from size variant

const flagVariants = cva("size-0", {
  variants: {
    color: {
      red: `border-t-white
           border-l-light-red
           border-b-white`,
    },
    size: {
      lg: `border-t-[50px]
           border-l-[85px]
           border-b-[50px]`,
    },
  },
});

Output:

<div class="size-0 border-t-white border-l-light-red border-b-white border-t-[50px] border-l-[85px] border-b-[50px]"></div>

It seems when using class-variance-authority and custom classes tailwind thinks the border color/width are the same property, it overrides some clases and does not apply both.

In this code snippet you can see how the component should look like.

*, ::before, ::after {
    box-sizing: border-box;
    border-width: 0;
    border-style: solid;
}

.bg {
  background-color: black;
  min-height: 200px;
  min-width: 200px;
  display: grid;
  place-items: center;
}

.size-0 {
  width: 0px;
  height: 0px;
}
.border-t-white {
  border-top-color: white;
}
.border-l-light-red {
  border-left-color: red;
}
.border-b-white {
  border-bottom-color: white;
}
.border-t-lg {
  border-top-width: 50px;
}
.border-l-lg-long {
  border-left-width: 85px;
}
.border-b-lg {
  border-bottom-width: 50px;
}
<div class="bg">  
  <div class="size-0 border-t-lg border-l-lg-long border-b-lg border-t-white border-l-light-red border-b-white" ></div>
</div>

How can I fix this? Thanks in advance.


Solution

  • You'd need to configure tailwind-merge to account for your custom Tailwind value keys. You'd use extendTailwindMerge() instead of twMerge(), so that you can pass your custom Tailwind theme keys to the appropriate class groups. This then lets tailwind-merge know which keys conflict with each other and which do not.

    // import { extendTailwindMerge } from 'tailwind-merge';
    
    const customTwMerge = extendTailwindMerge({
      extend: {
        theme: {
          colors: ['light-white', 'light-red'],
          borderWidth: ['lg', 'lg-long'],
        },
      },
    });
    
    function cn(...inputs) {
      return customTwMerge(clsx(inputs));
    }
    
    const customVariants = cva("size-0", {
      variants: {
        size: {
          lg: `border-t-lg
               border-l-lg-long
               border-b-lg`,
        },
        color: {
          red: `border-t-white
               border-l-light-red
               border-b-white`,
        },
      },
    });
    
    console.log(cn(customVariants({ color: 'red', size: 'lg' })));
    <script>window.exports = {}</script>
    <script src="https://unpkg.com/tailwind-merge@2.5.4/dist/bundle-cjs.js"></script>
    <script>window.tailwindMerge = window.exports; window.exports = {}</script>
    <script src="https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.min.js" integrity="sha256-IyETGWkWvmZiU9FWXnbmkk2C5DCbuHfw5EwHxzwy5mU=" crossorigin="anonymous"></script>
    <script>
    window.require = () => window.clsx;
    window.exports = {}
    </script>
    <script src="https://unpkg.com/class-variance-authority@0.7.0/dist/index.js"></script>