tailwindcss icon indicating copy to clipboard operation
tailwindcss copied to clipboard

CSS based functional `@utility` with same name results in missing classes

Open RobinMalfait opened this issue 9 months ago • 8 comments

When defining custom functional @utility with the same name for different purposes results in unwanted behavior.

If you want to use the same name, similar to how we can use text-red-500 and text-2xl then the order matters and only the first one "wins" right now.

@utility foo-* {
  color: --value(--color- *);
}

@utility foo-* {
  font-size: --spacing(--value(number));
}

In this case, foo-red-500 will work, but foo-123 will not.

Play: https://play.tailwindcss.com/1VmWKocy9O?file=css

If you swap the order, then foo-123 will win, but foo-red-500 won't result in anything.

@utility foo-* {
  font-size: --spacing(--value(number));
}

@utility foo-* {
  color: --value(--color- *);
}

Play: https://play.tailwindcss.com/71oBj8xrI1?file=css


The expected behavior is that both work.

RobinMalfait avatar Mar 04 '25 18:03 RobinMalfait

Defining both properties in the same utility works

@utility foo-* {
    font-size: --spacing(--value(number));
    color: --value(--color- *);
}

kevster7000 avatar Mar 06 '25 10:03 kevster7000

Yep you are correct! And that's also what we do internally for core plugins, but I think there is still something nice about separating them.

Play with combination: https://play.tailwindcss.com/OUN74Zyn9S?file=css

RobinMalfait avatar Mar 06 '25 11:03 RobinMalfait

I think im having this issue too but in a similar fashion, when i have a utility class with the same name but with extra letters appended - e.g btn-primary + btn-primaryAlt The btn-primaryAlt will be get removed when running the minify script

CarterStevens1 avatar Mar 10 '25 14:03 CarterStevens1

Adding to this issue to highlight similar issue where generated custom classes override each other if sharing same class prefix with a setup of

:root {
  ...
  --color-base-600: hsl(0, 0%, 42%);
  ...
}

@theme {
  --color-sub-600: var(--color-base-600);
  --text-title-h1: 3.5rem;
  --text-title-h1--line-height: 4rem;
  --text-title-h1--letter-spacing: -0.01em;
  --text-title-h1--font-weight: 600;
}

Ideally, I would want to be able to set both text-title-h1 and text-sub-600 classes on an element i.e. on a radix dialog title element

const DialogTitle = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...rest }, forwardedRef) => {
  return (
    <DialogPrimitive.Title
      ref={forwardedRef}
      className={cn("text-title-h1 text-sub-600", className)}
      {...rest}
    />
  );
});
DialogTitle.displayName = "DialogTitle";

However, upon render only text-sub-600 is applied and if I swap the order of classes text-title-h1 is applied. Following theme documentation as outlined here.

To fix the issue I had to utilise @layer utilities and define classes without --text prefix i.e. --text-title-h1 became

@layer utiltiies {
  .title-h1 {
    font-size: var(--text-title-h1);
    line-height: var(--text-title-h1--line-height);
    letter-spacing: var(--text-title-h1--letter-spacing);
    font-weight: var(--text-title-h1--font-weight);
  }
}

On second thought the utilities approach seems more correct, however, the new documentation can be misleading for defining new font sizes and colors especially given that applying custom color and font size will only apply the last defined class on an element.

mtthsnc avatar Mar 10 '25 21:03 mtthsnc

@mtthsnc I am not applying the style in Radix dialog but could you check the link https://play.tailwindcss.com/oDMwWmaBEH?file=css

yash-k-s-7span avatar Mar 18 '25 05:03 yash-k-s-7span

@RobinMalfait While using utility classes are single-purpose classes that apply specific styles, and you can combine them to create complex designs, allowing you to build responsive and consistent user interfaces efficiently. but you cannot use the same name to define the same utility either combine the styles or use a different name for defining the utility.

yash-k-s-7span avatar Mar 18 '25 06:03 yash-k-s-7span

@yash-k-s-7span thank you for providing an example. I eventually backtracked this this to tailwind-merge being the issue as it would remove duplicate prefix classes i.e. if multiple text-* classes were applied it would always favour the last applied class. So this was not an issue with tailwindcss but with tailwind-merge

mtthsnc avatar Mar 18 '25 07:03 mtthsnc

Adding to this issue to highlight similar issue where generated custom classes override each other if sharing same class prefix with a setup of

:root { ... --color-base-600: hsl(0, 0%, 42%); ... }

@theme { --color-sub-600: var(--color-base-600); --text-title-h1: 3.5rem; --text-title-h1--line-height: 4rem; --text-title-h1--letter-spacing: -0.01em; --text-title-h1--font-weight: 600; } Ideally, I would want to be able to set both text-title-h1 and text-sub-600 classes on an element i.e. on a radix dialog title element

const DialogTitle = React.forwardRef< React.ComponentRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>

(({ className, ...rest }, forwardedRef) => { return ( <DialogPrimitive.Title ref={forwardedRef} className={cn("text-title-h1 text-sub-600", className)} {...rest} /> ); }); DialogTitle.displayName = "DialogTitle"; However, upon render only text-sub-600 is applied and if I swap the order of classes text-title-h1 is applied. Following theme documentation as outlined here.

To fix the issue I had to utilise @layer utilities and define classes without --text prefix i.e. --text-title-h1 became

@layer utiltiies { .title-h1 { font-size: var(--text-title-h1); line-height: var(--text-title-h1--line-height); letter-spacing: var(--text-title-h1--letter-spacing); font-weight: var(--text-title-h1--font-weight); } } On second thought the utilities approach seems more correct, however, the new documentation can be misleading for defining new font sizes and colors especially given that applying custom color and font size will only apply the last defined class on an element.

I have run into the same issue, though when using the @layer utilities approach, we don't get intelisense for them.

liljamesjohn avatar Apr 29 '25 13:04 liljamesjohn