tailwind-csstailwind-css-4

Combining multiple utilities to avoid code duplication; attaching a prefix to --value due to utility name shortening


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)?


Solution

  • Utility with token

    In the @theme directive, custom namespaces can be declared, which we can reference with tokens using the --value() function.

    It'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.

    Arbitrary value

    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.