DEV Community

Nashe Omirro
Nashe Omirro

Posted on

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
    /* ... */
  }
}
Enter fullscreen mode Exit fullscreen mode

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>)
}
Enter fullscreen mode Exit fullscreen mode

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 {
  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

and then:

<button class="dip:btn text-sm">accept</button>
Enter fullscreen mode Exit fullscreen mode

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

@utility btn {
  @layer dip {
    /* ... */
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay