I thought I'd expand the set of default available transition utilities and declare my own. With just one, it's not much of an issue, but after 2-3, I notice quite significant code duplication.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@utility transition-underline-offset {
transition-property: text-underline-offset;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
@utility transition-decoration-color {
transition-property: text-decoration-color;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
@utility transition-link {
transition-property: color, opacity, text-underline-offset, text-decoration-color;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
</style>
<div class="
underline underline-offset-2 hover:underline-offset-8
transition-underline-offset
">
Hello World with text-underline-offset
</div>
<div class="
underline decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-decoration-color
">
Hello World with text-decoration-color
</div>
<div class="
opacity-50 hover:opacity-100 hover:text-pink-500 underline underline-offset-2 hover:underline-offset-8 decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-link
">
Hello World with link
</div>
I thought about creating a transition-*
utility where the utility name defines the content, but then, for example, I can no longer declare "link", and for the other two, the utility name becomes longer.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@utility transition-* {
transition-property: --value('text-underline-offset', 'text-decoration-color');
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
@utility transition-link {
transition-property: color, opacity, text-underline-offset, text-decoration-color;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
</style>
<div class="
underline underline-offset-2 hover:underline-offset-8
transition-text-underline-offset
">
Hello World with text-underline-offset (should use transition-text-underline-offset instead of transition-underline-offset)
</div>
<div class="
underline decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-text-decoration-color
">
Hello World with text-decoration-color (should use transition-text-decoration-color instead of transition-decoration-color)
</div>
<div class="
opacity-50 hover:opacity-100 hover:text-pink-500 underline underline-offset-2 hover:underline-offset-8 decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-link
">
Hello World with link (in this case, I had to declare the link twice redundantly)
</div>
How could I declare all three cases in a single utility, while keeping the class name logically short (so that the first two cases don't unnecessarily include the text-
prefix)?
In the @theme
directive, custom namespaces can be declared, which we can reference with tokens using the --value()
function.
@theme
directive - TailwindCSS v4 Docs--value
- TailwindCSS v4 DocsIt's important to avoid using existing namespaces for such special cases. So, using the --text-*
token is not reasonable since it's already reserved for other purposes.
However, in this case, it's neither taken nor illogical to introduce a new --transition-*
token. We can do this by listing key-value pairs inside the @theme
. The key names will be decisive when we want to refer to these values.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@theme {
--transition-underline-offset: text-underline-offset;
--transition-decoration-color: text-decoration-color;
--transition-link: color, opacity, text-underline-offset, text-decoration-color;
}
@utility transition-* {
transition-property: --value(--transition-*);
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
</style>
<div class="
underline underline-offset-2 hover:underline-offset-8
transition-underline-offset
">
Hello World with text-underline-offset
</div>
<div class="
underline decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-decoration-color
">
Hello World with text-decoration-color
</div>
<div class="
opacity-50 hover:opacity-100 hover:text-pink-500 underline underline-offset-2 hover:underline-offset-8 decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-link
">
Hello World with link
</div>
This way, we can also create transition groups under a unique name — for example, “link” — to which multiple properties can be listed, separated by commas.
An alternative is to define rarely used cases with arbitrary values without creating a utility:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<div class="
underline underline-offset-2 hover:underline-offset-8
transition-[text-underline-offset]
">
Hello World with text-underline-offset
</div>
<div class="
underline decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-[text-decoration-color]
">
Hello World with text-decoration-color
</div>
<div class="
opacity-50 hover:opacity-100 hover:text-pink-500 underline underline-offset-2 hover:underline-offset-8 decoration-2 decoration-sky-500 hover:decoration-pink-500
transition-[color,_opacity,_text-underline-offset,_text-decoration-color]
">
Hello World with link
</div>
Make sure to either avoid using spaces, or if you want to use them, replace spaces with the underscore (_
) character.