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:
<!-- `text-sm` will win against `text-2xl` -->
<h1 class="dip:text-2xl text-sm">This is a bad example</h1>
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:
<!-- `className` and `children` are props, you know the drill -->
<h1 class="dip:text-xl dip:text-surface-900 {className}">{children()}</h1>
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:
<!-- the cursed output to our frankenstein's monster -->
<h1 class="dip2:text-xl dip:text-2xl text-3xl">Oh bloody hell</h1>
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:
<button class="dip:btn text-sm">accept</button>
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.
Top comments (0)