The Joy of Extending Tailwind

dandep1 pts0 comments

The joy of extending Tailwind / Extend Tailwind / dan—webnotes

Skip to main content

2026-07-14

🔗 The joy of extending Tailwind

One of the biggest upgrade that came with Tailwind 4 has been the new CSS-based configuration and extensibility system.

The CSS configuration came with a new simple syntax, which is very close to the way regular CSS is written. This makes it really easy to integrate Tailwind into a project's design system.

Let's consider a regular CSS class.

.my-class {<br>display: block;<br>color: red;

This is a perfectly fine class, and here is the same class registered as a tailwind utility using the new directive @utility.

@utility my-class {<br>display: block;<br>color: red;

Registering a class as a @utility brings super-powers to that class, together with nice improvements in developer experience.

You can now apply that class arbitrarily whenever the design requires it, whether it is desktop-only md:my-class, on hover hover:my-class, when in dark-mode dark:my-class, and many others.

The class is added to the final stylesheet only in case the utility is actually used, avoiding unnecessary CSS.

You can hover a class to lookup its CSS declarations, thanks to the amazing VSCode tailwind-intellisense extension.

# Props in CSS

There aren't only static @utility, Tailwind v4 introduced a whole CSS-like syntax to pass values into the utilities it generates. These props can modify the values of the CSS properties within the class, and they can be numbers, colors, percentages, keywords, etc.

It's pretty straightforward to grasp how the new functions work if you are already familiar with CSS syntax and logic. The main functions are --alpha(), --value(), --modifier(), --default().

Check out the official documentation on how to add custom styles, to see how these functions work together, and how they allow to generate complex components on-the-fly. Here are a few examples.

# CSS shapes

Using clip-path and the modern CSS function polygon(), together with a size prop, we can generate any sort of CSS-only shapes.

@utility star-* {<br>width: calc(--value(number) * var(--spacing));<br>height: calc(--value(number) * var(--spacing));<br>clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);<br>flex-shrink: 0;

# A colored shadow border that blends in

If you are interested in subtle CSS details that make a UI feel polished, you will have probably encountered the border-shadow trick.

.border-shadow {<br>box-shadow:<br>0px 0px 0px 1px rgba(0, 0, 0, 0.06),<br>0px 1px 2px -1px rgba(0, 0, 0, 0.06),<br>0px 2px 4px 0px rgba(0, 0, 0, 0.04);

But now you can have this technique work with any color, and with any opacity modifier.

@utility border-shadow-* {<br>box-shadow:<br>0px 0px 0px 1px --alpha(--value(--color-*, [color]) / --modifier([percentage], 6%)),<br>0px 1px 2px -1px --alpha(--value(--color-*, [color]) / --modifier([percentage], 6%)),<br>0px 2px 4px 0px --alpha(--value(--color-*, [color]) / calc(--modifier([percentage], 6%) * 0.66));

# A conditional rounded box

Some time ago a post by Ahmad Shadeed gained popularity. It described a CSS one-liner which conditionally rounded an element depending whether the element touched the viewport (or its container) or not.

But for example, with tailwind we can have the one-liner also control how rounded the element initially was.

@utility conditional-rounded-* {<br>border-radius: calc(sign(100cqi - 100%) * --value(number) * var(--spacing));

# Creating variants

The last chapter of extending tailwind touches the modifiers , which specify when applying a class.

These use another new directive @custom-variant. Tailwind already offers variants for the most used CSS media queries. But creating new ones can still be valuable for a few reasons:

Ergonomics, create shorter, more clear directives

@custom-variant touch {<br>@media (pointer: coarse) {<br>@slot;

Patch browsers shortcomings , with ad-hoc inline fixes

@custom-variant safari {<br>@supports (font: -apple-system-body) {<br>@slot;

Combining multiple directives together

@custom-variant hocus {<br>@media (hover: hover) {<br>&:hover {<br>@slot;<br>&:focus-visible {<br>@slot;

# Utility-driven agents

It's funny to feel joy in polishing code snippets, while being told you shouldn't even read code anymore.

But I think even with AI writing all the code, it's still valuable to produce abstractions that makes frontend code more expressive and clear. (Granted, you have to like Tailwind)

Ultimately if it's good for humans, it's good for LLMs too.

Also, I still prefer burning tokens on crafting powerful abstractions that I, the LLMs, and my team can reuse over and over, rather than painfully steering a 100B parameter model into a writing a flex column that takes 2 seconds to type.

Recently I was getting annoyed at how long it was taking me to position content inside the typical 12-columns layout, and having it adapt to the breakpoints.

Once I had the right idea, it really took one rant prompt and pointing the LLMs to the...

class tailwind utility color value hover

Related Articles