10 / 30
All 30 in 30 Tailwind CSS Text Hover Effects
- 01Tailwind CSS Animated Underline Hover Effect
- 02Tailwind Liquid Text Fill Hover Animation
- 03Tailwind Gradient Text Hover Effect with bg-clip-text
- 04Tailwind Animated Strikethrough Text Hover Effect
- 05Tailwind Text Swap and Flip Up Hover Effect
- 06Tailwind CSS Glitch Text Hover Effect
- 07Tailwind Neon Glow Text with Flicker Animation
- 08Tailwind Metallic Gold Shimmer Text Effect
- 09Tailwind 3D Text Pop and Lift Hover Effect
- 10Tailwind Staggered Letter Wave Hover Animation
- 11Tailwind Spotlight Text Reveal on Mouse Hover
- 12Tailwind Hover to Reveal Hidden Spoiler Text
- 13Tailwind Blur to Focus Text Hover Effect
- 14Tailwind Magnetic Hover Effect for Buttons and Text
- 15Tailwind Letter Spacing Expand on Hover (Tracking Animation)
- 16Tailwind Outline Text Stroke to Fill Hover Effect
- 17Tailwind Text Scramble and Matrix Decrypt Hover Effect
- 18Tailwind Animated Border Button Hover Effect
- 19Tailwind Elastic Rubber Band Text Hover Animation
- 20Tailwind Variable Font Weight Hover Animation
- 21Tailwind Animated Rainbow Gradient Text on Hover
- 22Tailwind 3D Perspective Tilt Text Hover Effect
- 23Tailwind Smoke Dissolve Text Hover Effect
- 24Tailwind Fire Text Effect with Heat Wave Glow
- 25Tailwind Dynamic Text Shadow that Follows the Cursor
- 26Tailwind Infinite Text Marquee that Speeds Up on Hover
- 27Tailwind Duotone Split Color Text Hover Effect
- 28Tailwind CSS Tooltip on Hover (No JavaScript)
- 29Tailwind SVG Water Ripple Distortion Text Effect
- 30Tailwind Clip-Path Text Reveal Hover Animation
Tailwind Staggered Letter Wave Hover Animation
A juice-brand wordmark whose letters jump in a wave: each letter sets style="--i:N", and one class list on the parent — *:delay-[calc(var(--i)*45ms)] — staggers them all. Because it's transitions, not keyframes, leaving mid-wave reverses through the same stagger. The flavour pills reuse the trick at a smaller amplitude.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tth-10 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tth10-sky),var(--color-tth10-bg)_55%,var(--color-tth10-sand))] px-5 py-14 font-tth10-sans text-tth10-ink" aria-label="Staggered letter wave demo">
<div class="flex flex-col items-center gap-[clamp(16px,3vw,26px)] text-center">
<p class="text-[12.5px] font-extrabold uppercase tracking-[0.26em] text-tth10-mut">Cold-pressed · Bondi since 2019</p>
<a id="tth10-top" href="#tth10-top" aria-label="SUNDAZE" class="group/wave inline-block rounded-[10px] px-[0.1em] pt-[0.18em] pb-[0.1em] font-tth10-display text-[clamp(56px,13.5vw,140px)] leading-none tracking-[-0.01em] no-underline focus-visible:outline-3 focus-visible:outline-offset-8 focus-visible:outline-tth10-acc">
<span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,rotate,color] *:duration-500 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/wave:*:-translate-y-[0.22em] group-hover/wave:*:text-tth10-acc group-hover/wave:*:even:rotate-6 group-hover/wave:*:even:text-tth10-sea group-focus-visible/wave:*:-translate-y-[0.22em] group-focus-visible/wave:*:text-tth10-acc group-focus-visible/wave:*:even:rotate-6 group-focus-visible/wave:*:even:text-tth10-sea motion-reduce:*:transition-[color] motion-reduce:*:delay-0"><i style="--i:0">S</i><i style="--i:1">U</i><i style="--i:2">N</i><i style="--i:3">D</i><i style="--i:4">A</i><i style="--i:5">Z</i><i style="--i:6">E</i></span>
</a>
<nav class="flex flex-wrap justify-center gap-[clamp(10px,2.4vw,22px)]" aria-label="Flavours">
<a href="#tth10-top" aria-label="Mango" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">M</i><i style="--i:1">a</i><i style="--i:2">n</i><i style="--i:3">g</i><i style="--i:4">o</i></span></a>
<a href="#tth10-top" aria-label="Guava" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">G</i><i style="--i:1">u</i><i style="--i:2">a</i><i style="--i:3">v</i><i style="--i:4">a</i></span></a>
<a href="#tth10-top" aria-label="Yuzu" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">Y</i><i style="--i:1">u</i><i style="--i:2">z</i><i style="--i:3">u</i></span></a>
</nav>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth10-mut">Hover, leave mid-wave, hover again — transitions reverse through the same stagger, which keyframes can't do. The real word lives in <code class="rounded bg-tth10-chip px-1.5 py-px font-tth10-mono text-[0.9em]">aria-label</code>.</p>
</div>
</section><section class="tth-10 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tth10-sky),var(--color-tth10-bg)_55%,var(--color-tth10-sand))] px-5 py-14 font-tth10-sans text-tth10-ink" aria-label="Staggered letter wave demo">
<div class="flex flex-col items-center gap-[clamp(16px,3vw,26px)] text-center">
<p class="text-[12.5px] font-extrabold uppercase tracking-[0.26em] text-tth10-mut">Cold-pressed · Bondi since 2019</p>
<a id="tth10-top" href="#tth10-top" aria-label="SUNDAZE" class="group/wave inline-block rounded-[10px] px-[0.1em] pt-[0.18em] pb-[0.1em] font-tth10-display text-[clamp(56px,13.5vw,140px)] leading-none tracking-[-0.01em] no-underline focus-visible:outline-3 focus-visible:outline-offset-8 focus-visible:outline-tth10-acc">
<span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,rotate,color] *:duration-500 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/wave:*:-translate-y-[0.22em] group-hover/wave:*:text-tth10-acc group-hover/wave:*:even:rotate-6 group-hover/wave:*:even:text-tth10-sea group-focus-visible/wave:*:-translate-y-[0.22em] group-focus-visible/wave:*:text-tth10-acc group-focus-visible/wave:*:even:rotate-6 group-focus-visible/wave:*:even:text-tth10-sea motion-reduce:*:transition-[color] motion-reduce:*:delay-0"><i style="--i:0">S</i><i style="--i:1">U</i><i style="--i:2">N</i><i style="--i:3">D</i><i style="--i:4">A</i><i style="--i:5">Z</i><i style="--i:6">E</i></span>
</a>
<nav class="flex flex-wrap justify-center gap-[clamp(10px,2.4vw,22px)]" aria-label="Flavours">
<a href="#tth10-top" aria-label="Mango" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">M</i><i style="--i:1">a</i><i style="--i:2">n</i><i style="--i:3">g</i><i style="--i:4">o</i></span></a>
<a href="#tth10-top" aria-label="Guava" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">G</i><i style="--i:1">u</i><i style="--i:2">a</i><i style="--i:3">v</i><i style="--i:4">a</i></span></a>
<a href="#tth10-top" aria-label="Yuzu" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">Y</i><i style="--i:1">u</i><i style="--i:2">z</i><i style="--i:3">u</i></span></a>
</nav>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth10-mut">Hover, leave mid-wave, hover again — transitions reverse through the same stagger, which keyframes can't do. The real word lives in <code class="rounded bg-tth10-chip px-1.5 py-px font-tth10-mono text-[0.9em]">aria-label</code>.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bagel+Fat+One&family=Nunito:wght@600;800;900&family=JetBrains+Mono:wght@400;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bagel+Fat+One&family=Nunito:wght@600;800;900&family=JetBrains+Mono:wght@400;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Every
.wave idescendant rule moved to the parent span as*:utilities — the letters carry onlystyle="--i:N".i:nth-child(even)became the stacked*:even:variant. - UI upgrade from the CSS original: Bagel Fat One gives the wordmark a real juice-label personality, and the flavour pills now lift on a hard 3px offset shadow that turns orange — a tactile sticker feel.
- UI upgrade: under
motion-reduce:the letters still change colour (instantly, no stagger) instead of losing all feedback; muted copy went to0.43L (6.2:1).
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Staggered Letter Wave Hover Animation
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-staggered-letter-wave-hover-animation/
A juice-brand wordmark whose letters jump in a wave: each letter sets style="--i:N", and one class list on the parent — *:delay-[calc(var(--i)*45ms)] — staggers them all. Because it's transitions, not keyframes, leaving mid-wave reverses through the same stagger. The flavour pills reuse the trick at a smaller amplitude.
## HTML
```html
<section class="tth-10 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tth10-sky),var(--color-tth10-bg)_55%,var(--color-tth10-sand))] px-5 py-14 font-tth10-sans text-tth10-ink" aria-label="Staggered letter wave demo">
<div class="flex flex-col items-center gap-[clamp(16px,3vw,26px)] text-center">
<p class="text-[12.5px] font-extrabold uppercase tracking-[0.26em] text-tth10-mut">Cold-pressed · Bondi since 2019</p>
<a id="tth10-top" href="#tth10-top" aria-label="SUNDAZE" class="group/wave inline-block rounded-[10px] px-[0.1em] pt-[0.18em] pb-[0.1em] font-tth10-display text-[clamp(56px,13.5vw,140px)] leading-none tracking-[-0.01em] no-underline focus-visible:outline-3 focus-visible:outline-offset-8 focus-visible:outline-tth10-acc">
<span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,rotate,color] *:duration-500 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/wave:*:-translate-y-[0.22em] group-hover/wave:*:text-tth10-acc group-hover/wave:*:even:rotate-6 group-hover/wave:*:even:text-tth10-sea group-focus-visible/wave:*:-translate-y-[0.22em] group-focus-visible/wave:*:text-tth10-acc group-focus-visible/wave:*:even:rotate-6 group-focus-visible/wave:*:even:text-tth10-sea motion-reduce:*:transition-[color] motion-reduce:*:delay-0"><i style="--i:0">S</i><i style="--i:1">U</i><i style="--i:2">N</i><i style="--i:3">D</i><i style="--i:4">A</i><i style="--i:5">Z</i><i style="--i:6">E</i></span>
</a>
<nav class="flex flex-wrap justify-center gap-[clamp(10px,2.4vw,22px)]" aria-label="Flavours">
<a href="#tth10-top" aria-label="Mango" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">M</i><i style="--i:1">a</i><i style="--i:2">n</i><i style="--i:3">g</i><i style="--i:4">o</i></span></a>
<a href="#tth10-top" aria-label="Guava" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">G</i><i style="--i:1">u</i><i style="--i:2">a</i><i style="--i:3">v</i><i style="--i:4">a</i></span></a>
<a href="#tth10-top" aria-label="Yuzu" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">Y</i><i style="--i:1">u</i><i style="--i:2">z</i><i style="--i:3">u</i></span></a>
</nav>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth10-mut">Hover, leave mid-wave, hover again — transitions reverse through the same stagger, which keyframes can't do. The real word lives in <code class="rounded bg-tth10-chip px-1.5 py-px font-tth10-mono text-[0.9em]">aria-label</code>.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Staggered Letter Wave Hover Animation
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-staggered-letter-wave-hover-animation/
A juice-brand wordmark whose letters jump in a wave: each letter sets style="--i:N", and one class list on the parent — *:delay-[calc(var(--i)*45ms)] — staggers them all. Because it's transitions, not keyframes, leaving mid-wave reverses through the same stagger. The flavour pills reuse the trick at a smaller amplitude.
## HTML
```html
<section class="tth-10 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tth10-sky),var(--color-tth10-bg)_55%,var(--color-tth10-sand))] px-5 py-14 font-tth10-sans text-tth10-ink" aria-label="Staggered letter wave demo">
<div class="flex flex-col items-center gap-[clamp(16px,3vw,26px)] text-center">
<p class="text-[12.5px] font-extrabold uppercase tracking-[0.26em] text-tth10-mut">Cold-pressed · Bondi since 2019</p>
<a id="tth10-top" href="#tth10-top" aria-label="SUNDAZE" class="group/wave inline-block rounded-[10px] px-[0.1em] pt-[0.18em] pb-[0.1em] font-tth10-display text-[clamp(56px,13.5vw,140px)] leading-none tracking-[-0.01em] no-underline focus-visible:outline-3 focus-visible:outline-offset-8 focus-visible:outline-tth10-acc">
<span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,rotate,color] *:duration-500 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/wave:*:-translate-y-[0.22em] group-hover/wave:*:text-tth10-acc group-hover/wave:*:even:rotate-6 group-hover/wave:*:even:text-tth10-sea group-focus-visible/wave:*:-translate-y-[0.22em] group-focus-visible/wave:*:text-tth10-acc group-focus-visible/wave:*:even:rotate-6 group-focus-visible/wave:*:even:text-tth10-sea motion-reduce:*:transition-[color] motion-reduce:*:delay-0"><i style="--i:0">S</i><i style="--i:1">U</i><i style="--i:2">N</i><i style="--i:3">D</i><i style="--i:4">A</i><i style="--i:5">Z</i><i style="--i:6">E</i></span>
</a>
<nav class="flex flex-wrap justify-center gap-[clamp(10px,2.4vw,22px)]" aria-label="Flavours">
<a href="#tth10-top" aria-label="Mango" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">M</i><i style="--i:1">a</i><i style="--i:2">n</i><i style="--i:3">g</i><i style="--i:4">o</i></span></a>
<a href="#tth10-top" aria-label="Guava" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">G</i><i style="--i:1">u</i><i style="--i:2">a</i><i style="--i:3">v</i><i style="--i:4">a</i></span></a>
<a href="#tth10-top" aria-label="Yuzu" class="group/pill grid min-h-12 place-items-center rounded-full border-[2.5px] border-tth10-ink bg-tth10-paper px-[22px] text-[17px] font-black no-underline shadow-[0_3px_0_var(--color-tth10-ink)] transition-[background-color,border-color,translate,box-shadow] duration-300 hover:-translate-y-0.5 hover:border-tth10-acc hover:bg-white hover:shadow-[0_5px_0_var(--color-tth10-acc)] focus-visible:border-tth10-acc focus-visible:outline-3 focus-visible:outline-offset-[3px] focus-visible:outline-tth10-acc"><span aria-hidden="true" class="*:inline-block *:not-italic *:transition-[translate,color] *:duration-400 *:ease-[cubic-bezier(.34,1.9,.5,1)] *:delay-[calc(var(--i)*45ms)] group-hover/pill:*:-translate-y-[0.14em] group-hover/pill:*:text-tth10-acc group-focus-visible/pill:*:-translate-y-[0.14em] group-focus-visible/pill:*:text-tth10-acc motion-reduce:*:delay-0"><i style="--i:0">Y</i><i style="--i:1">u</i><i style="--i:2">z</i><i style="--i:3">u</i></span></a>
</nav>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth10-mut">Hover, leave mid-wave, hover again — transitions reverse through the same stagger, which keyframes can't do. The real word lives in <code class="rounded bg-tth10-chip px-1.5 py-px font-tth10-mono text-[0.9em]">aria-label</code>.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tth10-sky: #fbefcd;
--color-tth10-sky: oklch(0.95 0.05 90);
--color-tth10-bg: #f6e3b4;
--color-tth10-bg: oklch(0.93 0.06 85);
--color-tth10-sand: #f2d39b;
--color-tth10-sand: oklch(0.9 0.07 70);
--color-tth10-paper: #fcf6e6;
--color-tth10-paper: oklch(0.97 0.02 90);
--color-tth10-chip: #ecd9a8;
--color-tth10-chip: oklch(0.88 0.05 85);
--color-tth10-ink: #5a2d10;
--color-tth10-ink: oklch(0.32 0.08 50);
--color-tth10-mut: #6c4a33;
--color-tth10-mut: oklch(0.43 0.06 55);
--color-tth10-acc: #e2532c;
--color-tth10-acc: oklch(0.62 0.19 35);
--color-tth10-sea: #2b8cc0;
--color-tth10-sea: oklch(0.6 0.13 220);
--font-tth10-display: 'Bagel Fat One', system-ui, sans-serif;
--font-tth10-sans: 'Nunito', system-ui, sans-serif;
--font-tth10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tth-10 {
width: 100%;
min-height: 100svh;
}
```How this works
One class list for every letter. v4's *: variant targets direct children, so the letters themselves carry nothing but their index:
<span aria-hidden="true" class="*:inline-block
*:transition-[translate,rotate,color] *:duration-500
*:delay-[calc(var(--i)*45ms)]
group-hover/wave:*:-translate-y-[0.22em]
group-hover/wave:*:even:rotate-6">
<i style="--i:0">S</i><i style="--i:1">U</i>…Why transitions. A keyframe wave restarts from zero if you leave and re-enter. A transition reverses from wherever each letter is, so the wave unwinds smoothly — and the stagger plays in reverse order too.
The spring is the overshoot curve cubic-bezier(.34,1.9,.5,1): each letter overshoots upward and settles, so the wave feels elastic without bouncing keyframes.
Make it yours
- Faster wave: lower
45msto25ms. - Reverse direction: number the letters from the end (
--idescending). - Colour cycle: add
group-hover/wave:*:nth-3:text-…for a three-colour rhythm.
Gotchas — read before shipping
- Inline elements ignore
translate— the letters must beinline-block. - Splitting letters kills kerning pairs; use display faces with even spacing.
- Never let screen readers read S-U-N-D-A-Z-E: the real word lives in the link's
aria-label.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Universal; the floor is Tailwind v4's sheet. *: compiles to :is(.x > *).