02 / 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 Liquid Text Fill Hover Animation
A hollow wordmark that floods with water on hover: an outlined layer ([-webkit-text-stroke:…]) under a gradient layer clipped with bg-clip-text and a typed @property level that tweens from 0% to 104%, then bobs. The nav pills below fill bottom-up with bg-[length:100%_0%] → hover:bg-[length:100%_100%].
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tth-02 @container w-full min-h-svh grid place-items-center bg-tth02-bg bg-[radial-gradient(120%_90%_at_50%_0%,var(--color-tth02-deep),var(--color-tth02-bg)_60%)] px-5 py-14 font-tth02-sans text-tth02-ink" aria-label="Liquid text fill on hover demo">
<div class="flex w-full max-w-[52rem] flex-col items-center gap-6 text-center">
<p class="text-xs font-bold uppercase tracking-[0.28em] text-tth02-mut">Tidewater Swim Co. — Summer '26</p>
<a id="tth02-top" href="#tth02-top" class="group/word relative inline-block rounded px-[0.04em] py-[0.06em] font-tth02-display text-[clamp(44px,11cqi,118px)] font-black leading-[1.04] tracking-[-0.03em] no-underline [--tth02-fill:0%] transition-[--tth02-fill] duration-900 ease-[cubic-bezier(.3,.9,.2,1)] hover:[--tth02-fill:104%] focus-visible:[--tth02-fill:104%] focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-tth02-hi motion-reduce:transition-none">
<span class="block text-tth02-bg [paint-order:stroke_fill] [-webkit-text-stroke:3px_color-mix(in_oklch,var(--color-tth02-ink)_55%,transparent)]">TIDEWATER</span>
<span aria-hidden="true" class="absolute inset-0 block px-[0.04em] py-[0.06em] bg-[linear-gradient(180deg,var(--color-tth02-hi)_0%,var(--color-tth02-hi)_6%,var(--color-tth02-lo)_60%,var(--color-tth02-abyss)_100%)] bg-clip-text text-transparent [clip-path:inset(calc(100%-var(--tth02-fill)+var(--tth02-bob))_-6%_-6%_-6%)] group-hover/word:animate-tth02-bob group-focus-visible/word:animate-tth02-bob motion-reduce:animate-none!">TIDEWATER</span>
</a>
<p class="max-w-[52ch] text-[15px] leading-[1.7] text-tth02-mut">Hold the cursor on the wordmark and let it flood. The level is one typed <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">@property</code>; the wave is a 3s keyframe on the clip edge.</p>
<nav class="mt-1.5 flex flex-wrap justify-center gap-2.5" aria-label="Collection">
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Swimwear</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Wetsuits</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Open water</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Journal</a>
</nav>
<p class="text-[12.5px] text-tth02-mut">Nav pills fill bottom-up with <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">bg-[length:100%_0%]</code> alone — the one-element version of the same idea.</p>
</div>
</section><section class="tth-02 @container w-full min-h-svh grid place-items-center bg-tth02-bg bg-[radial-gradient(120%_90%_at_50%_0%,var(--color-tth02-deep),var(--color-tth02-bg)_60%)] px-5 py-14 font-tth02-sans text-tth02-ink" aria-label="Liquid text fill on hover demo">
<div class="flex w-full max-w-[52rem] flex-col items-center gap-6 text-center">
<p class="text-xs font-bold uppercase tracking-[0.28em] text-tth02-mut">Tidewater Swim Co. — Summer '26</p>
<a id="tth02-top" href="#tth02-top" class="group/word relative inline-block rounded px-[0.04em] py-[0.06em] font-tth02-display text-[clamp(44px,11cqi,118px)] font-black leading-[1.04] tracking-[-0.03em] no-underline [--tth02-fill:0%] transition-[--tth02-fill] duration-900 ease-[cubic-bezier(.3,.9,.2,1)] hover:[--tth02-fill:104%] focus-visible:[--tth02-fill:104%] focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-tth02-hi motion-reduce:transition-none">
<span class="block text-tth02-bg [paint-order:stroke_fill] [-webkit-text-stroke:3px_color-mix(in_oklch,var(--color-tth02-ink)_55%,transparent)]">TIDEWATER</span>
<span aria-hidden="true" class="absolute inset-0 block px-[0.04em] py-[0.06em] bg-[linear-gradient(180deg,var(--color-tth02-hi)_0%,var(--color-tth02-hi)_6%,var(--color-tth02-lo)_60%,var(--color-tth02-abyss)_100%)] bg-clip-text text-transparent [clip-path:inset(calc(100%-var(--tth02-fill)+var(--tth02-bob))_-6%_-6%_-6%)] group-hover/word:animate-tth02-bob group-focus-visible/word:animate-tth02-bob motion-reduce:animate-none!">TIDEWATER</span>
</a>
<p class="max-w-[52ch] text-[15px] leading-[1.7] text-tth02-mut">Hold the cursor on the wordmark and let it flood. The level is one typed <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">@property</code>; the wave is a 3s keyframe on the clip edge.</p>
<nav class="mt-1.5 flex flex-wrap justify-center gap-2.5" aria-label="Collection">
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Swimwear</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Wetsuits</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Open water</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Journal</a>
</nav>
<p class="text-[12.5px] text-tth02-mut">Nav pills fill bottom-up with <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">bg-[length:100%_0%]</code> alone — the one-element version of the same idea.</p>
</div>
</section>@import "tailwindcss";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
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";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
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=Unbounded:wght@400..900&family=Inter+Tight:wght@400;500;600;700&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=Unbounded:wght@400..900&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.cth-02__word:hover{--cth02-fill:104%}became the arbitrary-property varianthover:[--tth02-fill:104%]; the two@propertyrules are the only CSS left besides tokens.- The descendant rule
.word:hover .liquid{animation:…}becamegroup-hover/word:animate-tth02-bobon the layer itself. - UI upgrade from the CSS original: Unbounded's wide black cut gives the wordmark a real liquid-container silhouette (more interior to flood), and muted copy rose from
0.66to0.74lightness — 7.1:1 on the navy stage. - UI upgrade: the outline is a 3px stroke painted under a stage-coloured fill (
[paint-order:stroke_fill]), which hides the internal contour overlaps variable fonts show through a plain text-stroke and leaves a crisp 1.5px edge. Pills now animatecolorandborder-colorin the same transition list, so the text no longer flashes dark before the fill arrives.
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 Liquid Text Fill Hover Animation
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-liquid-text-fill-hover-animation/
A hollow wordmark that floods with water on hover: an outlined layer ([-webkit-text-stroke:…]) under a gradient layer clipped with bg-clip-text and a typed @property level that tweens from 0% to 104%, then bobs. The nav pills below fill bottom-up with bg-[length:100%_0%] → hover:bg-[length:100%_100%].
## HTML
```html
<section class="tth-02 @container w-full min-h-svh grid place-items-center bg-tth02-bg bg-[radial-gradient(120%_90%_at_50%_0%,var(--color-tth02-deep),var(--color-tth02-bg)_60%)] px-5 py-14 font-tth02-sans text-tth02-ink" aria-label="Liquid text fill on hover demo">
<div class="flex w-full max-w-[52rem] flex-col items-center gap-6 text-center">
<p class="text-xs font-bold uppercase tracking-[0.28em] text-tth02-mut">Tidewater Swim Co. — Summer '26</p>
<a id="tth02-top" href="#tth02-top" class="group/word relative inline-block rounded px-[0.04em] py-[0.06em] font-tth02-display text-[clamp(44px,11cqi,118px)] font-black leading-[1.04] tracking-[-0.03em] no-underline [--tth02-fill:0%] transition-[--tth02-fill] duration-900 ease-[cubic-bezier(.3,.9,.2,1)] hover:[--tth02-fill:104%] focus-visible:[--tth02-fill:104%] focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-tth02-hi motion-reduce:transition-none">
<span class="block text-tth02-bg [paint-order:stroke_fill] [-webkit-text-stroke:3px_color-mix(in_oklch,var(--color-tth02-ink)_55%,transparent)]">TIDEWATER</span>
<span aria-hidden="true" class="absolute inset-0 block px-[0.04em] py-[0.06em] bg-[linear-gradient(180deg,var(--color-tth02-hi)_0%,var(--color-tth02-hi)_6%,var(--color-tth02-lo)_60%,var(--color-tth02-abyss)_100%)] bg-clip-text text-transparent [clip-path:inset(calc(100%-var(--tth02-fill)+var(--tth02-bob))_-6%_-6%_-6%)] group-hover/word:animate-tth02-bob group-focus-visible/word:animate-tth02-bob motion-reduce:animate-none!">TIDEWATER</span>
</a>
<p class="max-w-[52ch] text-[15px] leading-[1.7] text-tth02-mut">Hold the cursor on the wordmark and let it flood. The level is one typed <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">@property</code>; the wave is a 3s keyframe on the clip edge.</p>
<nav class="mt-1.5 flex flex-wrap justify-center gap-2.5" aria-label="Collection">
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Swimwear</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Wetsuits</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Open water</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Journal</a>
</nav>
<p class="text-[12.5px] text-tth02-mut">Nav pills fill bottom-up with <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">bg-[length:100%_0%]</code> alone — the one-element version of the same idea.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
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 Liquid Text Fill Hover Animation
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-liquid-text-fill-hover-animation/
A hollow wordmark that floods with water on hover: an outlined layer ([-webkit-text-stroke:…]) under a gradient layer clipped with bg-clip-text and a typed @property level that tweens from 0% to 104%, then bobs. The nav pills below fill bottom-up with bg-[length:100%_0%] → hover:bg-[length:100%_100%].
## HTML
```html
<section class="tth-02 @container w-full min-h-svh grid place-items-center bg-tth02-bg bg-[radial-gradient(120%_90%_at_50%_0%,var(--color-tth02-deep),var(--color-tth02-bg)_60%)] px-5 py-14 font-tth02-sans text-tth02-ink" aria-label="Liquid text fill on hover demo">
<div class="flex w-full max-w-[52rem] flex-col items-center gap-6 text-center">
<p class="text-xs font-bold uppercase tracking-[0.28em] text-tth02-mut">Tidewater Swim Co. — Summer '26</p>
<a id="tth02-top" href="#tth02-top" class="group/word relative inline-block rounded px-[0.04em] py-[0.06em] font-tth02-display text-[clamp(44px,11cqi,118px)] font-black leading-[1.04] tracking-[-0.03em] no-underline [--tth02-fill:0%] transition-[--tth02-fill] duration-900 ease-[cubic-bezier(.3,.9,.2,1)] hover:[--tth02-fill:104%] focus-visible:[--tth02-fill:104%] focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-tth02-hi motion-reduce:transition-none">
<span class="block text-tth02-bg [paint-order:stroke_fill] [-webkit-text-stroke:3px_color-mix(in_oklch,var(--color-tth02-ink)_55%,transparent)]">TIDEWATER</span>
<span aria-hidden="true" class="absolute inset-0 block px-[0.04em] py-[0.06em] bg-[linear-gradient(180deg,var(--color-tth02-hi)_0%,var(--color-tth02-hi)_6%,var(--color-tth02-lo)_60%,var(--color-tth02-abyss)_100%)] bg-clip-text text-transparent [clip-path:inset(calc(100%-var(--tth02-fill)+var(--tth02-bob))_-6%_-6%_-6%)] group-hover/word:animate-tth02-bob group-focus-visible/word:animate-tth02-bob motion-reduce:animate-none!">TIDEWATER</span>
</a>
<p class="max-w-[52ch] text-[15px] leading-[1.7] text-tth02-mut">Hold the cursor on the wordmark and let it flood. The level is one typed <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">@property</code>; the wave is a 3s keyframe on the clip edge.</p>
<nav class="mt-1.5 flex flex-wrap justify-center gap-2.5" aria-label="Collection">
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Swimwear</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Wetsuits</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Open water</a>
<a href="#tth02-top" class="grid min-h-11 place-items-center rounded-full border-[1.5px] border-tth02-ink/30 px-6 text-sm font-semibold no-underline bg-[linear-gradient(180deg,var(--color-tth02-hi),var(--color-tth02-lo))] bg-no-repeat bg-[position:0_100%] bg-[length:100%_0%] transition-[background-size,color,border-color] duration-450 ease-[cubic-bezier(.3,.9,.2,1)] hover:bg-[length:100%_100%] hover:border-transparent hover:text-tth02-bg focus-visible:bg-[length:100%_100%] focus-visible:text-tth02-bg focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tth02-hi motion-reduce:transition-none">Journal</a>
</nav>
<p class="text-[12.5px] text-tth02-mut">Nav pills fill bottom-up with <code class="rounded-[5px] bg-tth02-chip px-1.5 py-px font-tth02-mono text-[0.88em] text-tth02-hi">bg-[length:100%_0%]</code> alone — the one-element version of the same idea.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Typed properties: the one thing utilities cannot declare. Without them the level snaps instead of tweening. */
@property --tth02-fill {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@property --tth02-bob {
syntax: '<percentage>';
inherits: true;
initial-value: 0%;
}
@theme {
--color-tth02-bg: #0b1424;
--color-tth02-bg: oklch(0.17 0.03 250);
--color-tth02-deep: #112241;
--color-tth02-deep: oklch(0.23 0.05 255);
--color-tth02-chip: #1b2a42;
--color-tth02-chip: oklch(0.27 0.04 250);
--color-tth02-ink: #ebf1f6;
--color-tth02-ink: oklch(0.95 0.01 230);
--color-tth02-mut: #9eabbe;
--color-tth02-mut: oklch(0.74 0.03 240);
--color-tth02-hi: #8fd6f5;
--color-tth02-hi: oklch(0.85 0.11 220);
--color-tth02-lo: #2f6de0;
--color-tth02-lo: oklch(0.55 0.19 255);
--color-tth02-abyss: #23409a;
--color-tth02-abyss: oklch(0.4 0.16 262);
--font-tth02-display: 'Unbounded', system-ui, sans-serif;
--font-tth02-sans: 'Inter Tight', system-ui, sans-serif;
--font-tth02-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tth02-bob: tth02-bob 3s ease-in-out 0.9s infinite;
@keyframes tth02-bob {
0%,
100% {
--tth02-bob: 0%;
}
50% {
--tth02-bob: 1.6%;
}
}
}
.tth-02 {
width: 100%;
min-height: 100svh;
}
```How this works
A typed level. @property --tth02-fill { syntax:'<percentage>' } makes the custom property interpolable, so the link can simply say transition-[--tth02-fill] duration-900 hover:[--tth02-fill:104%]. The liquid layer reads it inside its clip-path, so the clip edge rises like a water line.
<span class="absolute inset-0 bg-clip-text text-transparent
[clip-path:inset(calc(100%-var(--tth02-fill)+var(--tth02-bob))_-6%_-6%_-6%)]
group-hover/word:animate-tth02-bob">The wave is a second typed property, --tth02-bob, animated by a keyframe declared inside @theme next to --animate-tth02-bob — Tailwind only emits it because a utility uses it. Its 0.9s delay lets the fill land before the surface starts moving.
Make it yours
- Speed:
duration-900on the wordmark is the fill time; keep the bob delay equal. - Liquid colour: edit the three stops in the layer's
bg-[linear-gradient(…)]— or point them at new@themetokens. - Partial fill:
hover:[--tth02-fill:60%]stops at a progress level — useful for skill or goal meters.
Gotchas — read before shipping
- Without the
@propertyregistration the level snaps instead of tweening — that rule is the one piece that must stay CSS. - Tailwind arbitrary values need
calc()operators without spaces:calc(100%-var(--x))is emitted with proper spacing. - The duplicate liquid layer is
aria-hiddenso screen readers say TIDEWATER once.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property landed in Firefox 128, which matches Tailwind v4's floor. Older engines show the hollow outline and an instant full fill.