Simple Hover Animations with Tailwind

3 minute read Published

Improve user experience using invisible animation with Tailwind CSS.

A little animation can make or break a design. If the animation is done well it becomes invisible, and oftentimes you won’t even notice it’s there. Invisible animation can have the opposite effect, and become noticeable when not present. This lack of animation becomes most obvious when shifting back and forth between websites with global navigation where one has a subtle animation and the other does not. In this case, it was my Svelte Headless UI Starter which lacked the animation and stook out like a sore thumb.

Taking some inspiration from my favorite WordPress theme I decided to tackle the addition of invisible animation to the global navigation in my starter. Here I’ll share the finished result and a simple pattern you can use to obtain the safe effect. And it’s easy to achieve with utils already available in Tailwind.

Here’s the final result:

Can’t see the video? Try it online with a pointing device.

An effect like this would have traditionally been achieved using pseudo-elements as similar to the examples examples provided on Codrops. However, Tailwind docs make an argument against using pseudo-elements. Their argument is that not using the pseudo-element is less code. In the case of Tailwind, they’re right.

There’s another benefit to not using pseudo-elements and that’s the need to use WIA-ARIA attributes in the markup which is a useful habit to get into. Here’s the code required to achieve the above invisible animation sans pseudo-elements pulled straight from the AGPL-licensed Svelte starter linked above:

{#each navigation as { href, name, current } (name)}
  <a
    {href}
    sveltekit:prefetch
    class={classes(
      current ? 'bg-gray-900 text-white' : 'text-gray-300',
      'relative px-3 py-2 rounded-md text-sm font-medium group'
    )}
    aria-current={current ? 'page' : undefined}
  >
    <span
      aria-hidden="true"
      class="absolute top-1/2 left-1/2 h-full w-0 -translate-x-1/2 -translate-y-1/2 rounded-md bg-gray-700 opacity-0 transition-all group-hover:w-full group-hover:opacity-100"
    />
    <span class="relative group-hover:text-white">{name}</span>
  </a>
{/each}

The important bits here are the addition of an absolutely-positioned span element within the anchor tag body along with the relatively positioned anchor text within a relatively positioned container. A group is used to allow child elements within the anchor to respond to hover events in tandem – one performing the background transition while the other highlights text.

From an animation standpoint, we’re simply transitioning the width and opacity of a positioned element within the anchor using Tailwind transition functions. The transition-all utility is used over standard transition to enable size transitions within Tailwind.

Notice the animated span has ARIA attribute aria-hidden="true". This is a queue to aural readers not to consider the span a structural part of the page – since we’re only using it for trim. Had we used Pseudo-elements to achieve the effect we could quite likely end with with an accessibility issue as browsers like Firefox, for example, tend to treat pseudo-element content differently than other browsers as I discovered while accessibility testing After Dark.

And that’s all there is to it. A simple approach to hover animation with Tailwind CSS that’s invisible to the user. Just keep in mind users will start to notice when they’re not present as something will simply feel “off” when they’re interacting with a user interface without them as it will feel more rigid.