making tailwind components more viable with "dip:"

So before diving into this, this code straight up isn't mine and I just found it on github written by gustavopch which is just an implementation of a tweet from tailwind's creator, Adam Wathan. what is it It's basically just a custom variant that does one thing, places utilities you use in a lower specificity: @variant dip { @layer dip { @slot; } } the above variant will place all CSS properties on a layer called dip. Since all tailwind utilities go in a layer called utilities, dip is actually a child of that initial layer. And with the way CSS handles layers, rules that are not in a layer will always beat out rules that are, so in this case the utilities layer always beats out the utilities.dip layer. Which makes us able to do something like this: This is a bad example again text-sm is not in the parent layer, so it beats the child layer that text-2xl resides in. what are the use cases the title of the article, components! specifically overriding their styling, and more specifically, creating components that are utilities *wink* let's start with the former, which to be honest kinda sucks and tw-merge is still better. Imagine this is a component in your favorite framework: {children()} any other class given to it can cleanly override text-xl and text-surface-900! Just like you would with tw-merge, only this time no javascript needed. but still, the reason why tw-merge is better is because you can't stack the dip! Once you've already overriden a class you can't do it again! Imagine this: // is used and "overriden" by... // which is also used by... // which "overrides" it again if SpecialHeading overrides BaseHeading and then Article overrides SpecialHeading it now doesn't work because we can't dip any deeper, for this example to work we have to use two layers not just one, okay maybe we can do two layers then, but what if the overriding goes for a third time? Don't even think about it: Oh bloody hell so yes, you can use dip: like the above with some heavy limitations but I'm more excited about the second use-case. the true use case fools! If you're reading this far in the article then you have fallen right into my trap! Because you see, this was actually @layer components propaganda! With the ability to place utilities into lower specificities, we are able to create utilities that are supposed to be overriden! @utility btn { @layer dip { color: var(--color-primary-500); padding: --spacing(2); /* ... */ } } and what's more, they can be used with variants! and intellisense autocompletes it with added tooltips! Now before you spit on me and recite your mindless "tailwind actually is against using component classes", imagine with me for a moment, look at how awful this is: // in a UI library somewhere... type Props = HTMLAttributes & { ... } function Btn({ class: className, ...props }: Props) { return ({children()}) } the shit you have to go through just so you could group a bunch of utility classes together, the typescript, the props, the runtime merging, the added abstraction, and then what if you want to make it an anchor tag instead? Oh but of course any library worth its salt has to have polymorphic components! Embrace the taboo with me for a sec, look at how this can be all done with just using CSS: @utility btn { /* ... */ } and then: accept we can even ditch dip: when we don't need to override it! Or just dip it by default with: @utility btn { @layer dip { /* ... */ } } you can use @apply inside by the way, and why must you have multiple components for variants when you could do: @utility btn { /* ... */ } @utility special-btn { @apply btn; /* ... */ } I could keep glazing but in short tailwind v4 has a lot of flexibility and power that we could utilize to remove unnecessary abstractions. In truth, I would agree with the "you don't need component classes" but only in a sense that you don't need a lot of them, but just not needing them at all seems impractical. conclusion So.. what do you think? Honestly I'm just glad component classes can now have autocomplete, back in v3 you had to do it with javascript as a plugin. The dip: variant is interesting but the big weakness of it being just "override-able once" is a big downer, for a lot of big apps out there I don't think they'll be dropping tw-merge anytime soon, but for small to mid sized apps I think it would be great. I'm more interested on your thoughts with the "true use case" claim though, I really think it's worth making a @component rule that is the same with @utility but instead places them in the components layer, the layer is just too under-utilized. Also p.s, tailwind's actually fine with component classes, i don't know why I thought they didn't.

Mar 20, 2025 - 20:50
 0
making tailwind components more viable with "dip:"

So before diving into this, this code straight up isn't mine and I just found it on github written by gustavopch which is just an implementation of a tweet from tailwind's creator, Adam Wathan.

what is it

It's basically just a custom variant that does one thing, places utilities you use in a lower specificity:

@variant dip {
  @layer dip {
    @slot;
  }
}

the above variant will place all CSS properties on a layer called dip. Since all tailwind utilities go in a layer called utilities, dip is actually a child of that initial layer.

And with the way CSS handles layers, rules that are not in a layer will always beat out rules that are, so in this case the utilities layer always beats out the utilities.dip layer. Which makes us able to do something like this:


 class="dip:text-2xl text-sm">This is a bad example

again text-sm is not in the parent layer, so it beats the child layer that text-2xl resides in.

what are the use cases

the title of the article, components! specifically overriding their styling, and more specifically, creating components that are utilities *wink*

let's start with the former, which to be honest kinda sucks and tw-merge is still better. Imagine this is a component in your favorite framework:


 class="dip:text-xl dip:text-surface-900 {className}">{children()}

any other class given to it can cleanly override text-xl and text-surface-900! Just like you would with tw-merge, only this time no javascript needed.

but still, the reason why tw-merge is better is because you can't stack the dip! Once you've already overriden a class you can't do it again! Imagine this:

<BaseHeading /> // is used and "overriden" by...
<SpecialHeading /> // which is also used by...
<Article /> // which "overrides" it again

if SpecialHeading overrides BaseHeading and then Article overrides SpecialHeading it now doesn't work because we can't dip any deeper, for this example to work we have to use two layers not just one, okay maybe we can do two layers then, but what if the overriding goes for a third time? Don't even think about it:


 class="dip2:text-xl dip:text-2xl text-3xl">Oh bloody hell

so yes, you can use dip: like the above with some heavy limitations but I'm more excited about the second use-case.

the true use case

fools! If you're reading this far in the article then you have fallen right into my trap! Because you see, this was actually @layer components propaganda!

With the ability to place utilities into lower specificities, we are able to create utilities that are supposed to be overriden!

@utility btn {
  @layer dip {
    color: var(--color-primary-500);
    padding: --spacing(2);
    /* ... */
  }
}

and what's more, they can be used with variants! and intellisense autocompletes it with added tooltips! Now before you spit on me and recite your mindless "tailwind actually is against using component classes", imagine with me for a moment, look at how awful this is:

// in a UI library somewhere...
type Props = HTMLAttributes<'button'> & { ... } 
function Btn({ class: className, ...props }: Props) {
  return (<button class={merge("...", className)} {...props}>{children()}button>)
}

the shit you have to go through just so you could group a bunch of utility classes together, the typescript, the props, the runtime merging, the added abstraction, and then what if you want to make it an anchor tag instead? Oh but of course any library worth its salt has to have polymorphic components!

Embrace the taboo with me for a sec, look at how this can be all done with just using CSS:

@utility btn {
  /* ... */
}

and then:

 class="dip:btn text-sm">accept

we can even ditch dip: when we don't need to override it! Or just dip it by default with:

@utility btn {
  @layer dip {
    /* ... */
  }
}

you can use @apply inside by the way, and why must you have multiple components for variants when you could do:

@utility btn {
  /* ... */
}

@utility special-btn {
  @apply btn;
  /* ... */
}

I could keep glazing but in short tailwind v4 has a lot of flexibility and power that we could utilize to remove unnecessary abstractions. In truth, I would agree with the "you don't need component classes" but only in a sense that you don't need a lot of them, but just not needing them at all seems impractical.

conclusion

So.. what do you think? Honestly I'm just glad component classes can now have autocomplete, back in v3 you had to do it with javascript as a plugin. The dip: variant is interesting but the big weakness of it being just "override-able once" is a big downer, for a lot of big apps out there I don't think they'll be dropping tw-merge anytime soon, but for small to mid sized apps I think it would be great.

I'm more interested on your thoughts with the "true use case" claim though, I really think it's worth making a @component rule that is the same with @utility but instead places them in the components layer, the layer is just too under-utilized.

Also p.s, tailwind's actually fine with component classes, i don't know why I thought they didn't.