03 / 20
All 20 in 20 Tailwind CSS Animated Buttons
- 01Tailwind Neon Glow Button with Pulsing Halo
- 02Tailwind Background Slide Hover Button (Four Directions)
- 03Tailwind Ripple Effect Button with No JavaScript
- 04Tailwind Animated Border Draw Button with Staged Delays
- 05Tailwind 3D Press Effect Button with Keycap Depth
- 06Tailwind Liquid Fill Button with Animated Wave Crest
- 07Tailwind Moving Gradient CTA Button
- 08Tailwind Shine Sweep Hover Button with Gloss Streak
- 09Tailwind Jelly Bounce Button with Elastic Squash
- 10Tailwind Text Flip Button Hover with 3D Rotation
- 11Tailwind Animated CTA Hero Button with Beacon Rings & Particle Burst
- 12Tailwind Submit Button Success State with Checkmark Draw
- 13Tailwind Animated Download Button with Progress Fill
- 14Tailwind Hamburger to Close Button with aria-expanded Variants
- 15Tailwind Add to Cart Button with Flying Product Dot
- 16Tailwind Social Share Buttons with Brand Hover & Copy Link
- 17Tailwind Animated Toggle Switch with peer-checked & has-checked
- 18Tailwind Loading Spinner Inside Button with State Machine
- 19Tailwind Next & Prev Arrow Buttons with Chasing Icons
- 20Tailwind Ghost Button with Cursor-Aware Background Reveal
Tailwind Ripple Effect Button with No JavaScript
A Material-style ripple that fires on press with no JavaScript at all. The port's interesting line is active:after:animate-tab03-ripple — two variants stacked, reading "while pressed, run the ripple on my own after-layer", which is the entire behaviour in one class.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tab-03 w-full min-h-svh grid place-items-center bg-tab03-bg font-tab03-sans px-6 py-10">
<div class="flex flex-col items-center gap-8">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab03-violet/70 m-0">Click or tap to trigger</p>
<div class="flex flex-wrap justify-center gap-4">
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-violet rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-violet after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Get Started</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-teal rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-teal after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Learn More</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-rose rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-rose after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Subscribe</button>
</div>
</div>
</section><section class="tab-03 w-full min-h-svh grid place-items-center bg-tab03-bg font-tab03-sans px-6 py-10">
<div class="flex flex-col items-center gap-8">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab03-violet/70 m-0">Click or tap to trigger</p>
<div class="flex flex-wrap justify-center gap-4">
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-violet rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-violet after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Get Started</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-teal rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-teal after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Learn More</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-rose rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-rose after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Subscribe</button>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}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-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}<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=Space+Grotesk:wght@400;500;600;700&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=Space+Grotesk:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
active:after:is the port's whole story: a stacked variant expresses:active::afterexactly, with no loss.- The keyframe animates
width/heightrather thanscale, matching the original. Scaling a fixed-size disk would be cheaper, but it changes the easing feel — a faithful port keeps the original's motion. - UI change from the CSS original: per-variant
focus-visiblerings, so the button is not keyboard-invisible while being press-reactive.
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 Ripple Effect Button with No JavaScript
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-ripple-effect-button-no-javascript/
A Material-style ripple that fires on press with no JavaScript at all. The port's interesting line is active:after:animate-tab03-ripple — two variants stacked, reading "while pressed, run the ripple on my own after-layer", which is the entire behaviour in one class.
## HTML
```html
<section class="tab-03 w-full min-h-svh grid place-items-center bg-tab03-bg font-tab03-sans px-6 py-10">
<div class="flex flex-col items-center gap-8">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab03-violet/70 m-0">Click or tap to trigger</p>
<div class="flex flex-wrap justify-center gap-4">
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-violet rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-violet after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Get Started</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-teal rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-teal after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Learn More</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-rose rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-rose after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Subscribe</button>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}
```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 Ripple Effect Button with No JavaScript
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-ripple-effect-button-no-javascript/
A Material-style ripple that fires on press with no JavaScript at all. The port's interesting line is active:after:animate-tab03-ripple — two variants stacked, reading "while pressed, run the ripple on my own after-layer", which is the entire behaviour in one class.
## HTML
```html
<section class="tab-03 w-full min-h-svh grid place-items-center bg-tab03-bg font-tab03-sans px-6 py-10">
<div class="flex flex-col items-center gap-8">
<p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab03-violet/70 m-0">Click or tap to trigger</p>
<div class="flex flex-wrap justify-center gap-4">
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-violet rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-violet after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Get Started</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-teal rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-teal after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Learn More</button>
<button type="button" class="relative overflow-hidden min-h-11 px-8 text-[0.95rem] font-bold tracking-[0.03em] text-white bg-tab03-rose rounded-[10px] cursor-pointer transition-[filter] duration-200 hover:brightness-110 active:brightness-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab03-rose after:content-[''] after:absolute after:top-1/2 after:left-1/2 after:size-0 after:-translate-x-1/2 after:-translate-y-1/2 after:rounded-full after:bg-white/40 after:opacity-0 after:pointer-events-none active:after:animate-tab03-ripple motion-reduce:active:after:animate-none">Subscribe</button>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab03-bg: #0f0c1a;
--color-tab03-bg: oklch(0.14 0.03 295);
--color-tab03-violet: #7c3aed;
--color-tab03-violet: oklch(0.53 0.25 295);
--color-tab03-teal: #0d9488;
--color-tab03-teal: oklch(0.56 0.11 184);
--color-tab03-rose: #e11d48;
--color-tab03-rose: oklch(0.57 0.22 13);
--font-tab03-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards;
}
@keyframes tab03-ripple {
0% {
width: 0;
height: 0;
opacity: 0.4;
}
80% {
width: 400%;
height: 400%;
opacity: 0.1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
.tab-03 {
width: 100%;
min-height: 100svh;
}
.tab-03 ::selection {
background: var(--color-tab03-violet);
color: #fff;
}
```How this works
Two variants, one behaviour. active: and after: compose left-to-right, so active:after:animate-tab03-ripple compiles to .btn:active::after{animation:…}. The keyframe grows the disk to 500% while fading opacity, and because it is bound to :active it resets on release with no cleanup code.
The seed disk is four utilities. after:size-0 after:rounded-full plus the two centring translates put the origin at the button centre at any size. The ripple is white at 40% alpha, which reads on all three fills.
It still cannot follow the cursor. That is a property of the technique, not the port: a pure-CSS ripple has no access to pointer coordinates. Demo 20 is the cursor-aware version, and it needs JS for exactly that reason.
Make it yours
- Origin: swap the centring translates for
after:top-[80%] after:left-[10%]to burst from a corner. - Speed lives in the token —
--animate-tab03-ripple: tab03-ripple 0.6s linear forwards. - On light fills, change the disk to
after:bg-black/10so the ripple stays visible.
Gotchas — read before shipping
- iOS Safari only fires
:activeon elements that look interactive — keepcursor-pointeron the button or the ripple never plays on iPhone. overflow-hiddenis what makes the pill shape clip the disk. Remove it and a 500% circle escapes the button on all four sides.- Do not animate
sizeon the button itself — only the pseudo-element. The keyframe touches layout, and confining it to the after-layer is what keeps that cheap.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:active and keyframe animations are universally supported; the floor is Tailwind v4's generated sheet.