12 CSS Ripple Effects02 / 12
CSS Ripple Button Click Animation
A full Material-style ink-ripple button kit in eight color variants — fire, ocean, emerald, violet, gold, rose, ghost and dark-surface — each with a radial ink span injected at the click coordinate that scales to fill and fades. A live code snippet panel explains the technique. Inter on a near-black surface.
Published
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="rpl-02">
<div class="rpl-02__header">
<h1>Ripple Button Click Animation</h1>
<p>Material-style ink ripple on every button press — pure CSS + minimal JS</p>
</div>
<div class="rpl-02__grid">
<button class="rpl-02__btn rpl-02__btn--fire">
<span class="rpl-02__icon">🔥</span> Get Started
</button>
<button class="rpl-02__btn rpl-02__btn--ocean">
<span class="rpl-02__icon">🌊</span> Dive In
</button>
<button class="rpl-02__btn rpl-02__btn--emerald">
<span class="rpl-02__icon">✓</span> Confirm
</button>
<button class="rpl-02__btn rpl-02__btn--violet">
<span class="rpl-02__icon">✦</span> Upgrade
</button>
<button class="rpl-02__btn rpl-02__btn--gold">
<span class="rpl-02__icon">★</span> Premium
</button>
<button class="rpl-02__btn rpl-02__btn--rose">
<span class="rpl-02__icon">♥</span> Save
</button>
<button class="rpl-02__btn rpl-02__btn--ghost">
Ghost Style
</button>
<button class="rpl-02__btn rpl-02__btn--dark">
Dark Surface
</button>
</div>
<div class="rpl-02__code">
<h3>How it works</h3>
<pre><span class="cm">/* On click, inject a positioned ink div */</span>
<span class="kw">.btn</span> { position: relative; overflow: hidden; }
<span class="kw">.ink</span> {
position: absolute; border-radius: 50%;
animation: <span class="str">ink-expand</span> 0.6s linear forwards;
}
@keyframes <span class="str">ink-expand</span> {
to { transform: scale(4); opacity: 0; }
}</pre>
</div>
</div><div class="rpl-02">
<div class="rpl-02__header">
<h1>Ripple Button Click Animation</h1>
<p>Material-style ink ripple on every button press — pure CSS + minimal JS</p>
</div>
<div class="rpl-02__grid">
<button class="rpl-02__btn rpl-02__btn--fire">
<span class="rpl-02__icon">🔥</span> Get Started
</button>
<button class="rpl-02__btn rpl-02__btn--ocean">
<span class="rpl-02__icon">🌊</span> Dive In
</button>
<button class="rpl-02__btn rpl-02__btn--emerald">
<span class="rpl-02__icon">✓</span> Confirm
</button>
<button class="rpl-02__btn rpl-02__btn--violet">
<span class="rpl-02__icon">✦</span> Upgrade
</button>
<button class="rpl-02__btn rpl-02__btn--gold">
<span class="rpl-02__icon">★</span> Premium
</button>
<button class="rpl-02__btn rpl-02__btn--rose">
<span class="rpl-02__icon">♥</span> Save
</button>
<button class="rpl-02__btn rpl-02__btn--ghost">
Ghost Style
</button>
<button class="rpl-02__btn rpl-02__btn--dark">
Dark Surface
</button>
</div>
<div class="rpl-02__code">
<h3>How it works</h3>
<pre><span class="cm">/* On click, inject a positioned ink div */</span>
<span class="kw">.btn</span> { position: relative; overflow: hidden; }
<span class="kw">.ink</span> {
position: absolute; border-radius: 50%;
animation: <span class="str">ink-expand</span> 0.6s linear forwards;
}
@keyframes <span class="str">ink-expand</span> {
to { transform: scale(4); opacity: 0; }
}</pre>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
.rpl-02,
.rpl-02 *,
.rpl-02 *::before,
.rpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rpl-02 ::selection {
background: #ff6b35;
color: #fff;
}
.rpl-02 {
--bg: #0f0f13;
--surface: #1a1a23;
--border: rgba(255,255,255,0.08);
--text: #f0f0f5;
--muted: rgba(240,240,245,0.45);
font-family: 'Inter', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 56px;
padding: 60px 24px;
color: var(--text);
}
.rpl-02__header {
text-align: center;
}
.rpl-02__header h1 {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.03em;
}
.rpl-02__header p {
margin-top: 10px;
color: var(--muted);
font-weight: 300;
font-size: 0.95rem;
}
.rpl-02__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: min(860px, 100%);
}
/* The core ripple button */
.rpl-02__btn {
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
font-family: 'Inter';
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.01em;
padding: 18px 32px;
border-radius: 12px;
color: #fff;
transition: transform 0.15s, filter 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 60px;
}
.rpl-02__btn:hover {
transform: translateY(-2px);
filter: brightness(1.08);
}
.rpl-02__btn:active {
transform: translateY(1px);
filter: brightness(0.95);
}
/* Ripple ink */
.rpl-02__btn .ink {
position: absolute;
border-radius: 50%;
transform: scale(0);
pointer-events: none;
animation: rpl-02-ink 0.6s linear forwards;
}
@keyframes rpl-02-ink {
to {
transform: scale(4);
opacity: 0;
}
}
/* Variant colors */
.rpl-02__btn--fire {
background: linear-gradient(135deg, #ff4500, #ff6b35);
box-shadow: 0 8px 32px rgba(255,69,0,0.35);
}
.rpl-02__btn--fire .ink {
background: rgba(255,200,150,0.45);
}
.rpl-02__btn--ocean {
background: linear-gradient(135deg, #0077b6, #00b4d8);
box-shadow: 0 8px 32px rgba(0,119,182,0.35);
}
.rpl-02__btn--ocean .ink {
background: rgba(160,230,255,0.4);
}
.rpl-02__btn--emerald {
background: linear-gradient(135deg, #065f46, #10b981);
box-shadow: 0 8px 32px rgba(16,185,129,0.3);
}
.rpl-02__btn--emerald .ink {
background: rgba(160,255,210,0.4);
}
.rpl-02__btn--violet {
background: linear-gradient(135deg, #5b21b6, #8b5cf6);
box-shadow: 0 8px 32px rgba(139,92,246,0.35);
}
.rpl-02__btn--violet .ink {
background: rgba(220,190,255,0.4);
}
.rpl-02__btn--gold {
background: linear-gradient(135deg, #92400e, #f59e0b);
box-shadow: 0 8px 32px rgba(245,158,11,0.3);
}
.rpl-02__btn--gold .ink {
background: rgba(255,245,180,0.45);
}
.rpl-02__btn--rose {
background: linear-gradient(135deg, #9f1239, #fb7185);
box-shadow: 0 8px 32px rgba(251,113,133,0.35);
}
.rpl-02__btn--rose .ink {
background: rgba(255,200,210,0.4);
}
/* Outlined ghost variant */
.rpl-02__btn--ghost {
background: transparent;
border: 1.5px solid rgba(255,255,255,0.25);
color: var(--text);
box-shadow: none;
}
.rpl-02__btn--ghost:hover {
border-color: rgba(255,255,255,0.55);
background: rgba(255,255,255,0.04);
}
.rpl-02__btn--ghost .ink {
background: rgba(255,255,255,0.15);
}
/* Dark surface variant */
.rpl-02__btn--dark {
background: var(--surface);
border: 1.5px solid var(--border);
color: var(--text);
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.rpl-02__btn--dark .ink {
background: rgba(255,255,255,0.12);
}
/* Icon pill */
.rpl-02__icon {
font-size: 1.1rem;
line-height: 1;
}
/* Code snippet */
.rpl-02__code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: min(860px, 100%);
}
.rpl-02__code h3 {
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 14px;
}
.rpl-02__code pre {
font-size: 0.82rem;
line-height: 1.65;
color: rgba(240,240,245,0.75);
font-family: 'Courier New', monospace;
white-space: pre-wrap;
word-break: break-word;
}
.rpl-02__code .kw {
color: #c084fc;
}
.rpl-02__code .str {
color: #86efac;
}
.rpl-02__code .cm {
color: rgba(240,240,245,0.3);
font-style: italic;
}
@media (prefers-reduced-motion: reduce) {
.rpl-02__btn .ink {
animation: none !important;
}
.rpl-02__btn {
transition: none;
}
}@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
.rpl-02,
.rpl-02 *,
.rpl-02 *::before,
.rpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rpl-02 ::selection {
background: #ff6b35;
color: #fff;
}
.rpl-02 {
--bg: #0f0f13;
--surface: #1a1a23;
--border: rgba(255,255,255,0.08);
--text: #f0f0f5;
--muted: rgba(240,240,245,0.45);
font-family: 'Inter', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 56px;
padding: 60px 24px;
color: var(--text);
}
.rpl-02__header {
text-align: center;
}
.rpl-02__header h1 {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.03em;
}
.rpl-02__header p {
margin-top: 10px;
color: var(--muted);
font-weight: 300;
font-size: 0.95rem;
}
.rpl-02__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: min(860px, 100%);
}
/* The core ripple button */
.rpl-02__btn {
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
font-family: 'Inter';
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.01em;
padding: 18px 32px;
border-radius: 12px;
color: #fff;
transition: transform 0.15s, filter 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 60px;
}
.rpl-02__btn:hover {
transform: translateY(-2px);
filter: brightness(1.08);
}
.rpl-02__btn:active {
transform: translateY(1px);
filter: brightness(0.95);
}
/* Ripple ink */
.rpl-02__btn .ink {
position: absolute;
border-radius: 50%;
transform: scale(0);
pointer-events: none;
animation: rpl-02-ink 0.6s linear forwards;
}
@keyframes rpl-02-ink {
to {
transform: scale(4);
opacity: 0;
}
}
/* Variant colors */
.rpl-02__btn--fire {
background: linear-gradient(135deg, #ff4500, #ff6b35);
box-shadow: 0 8px 32px rgba(255,69,0,0.35);
}
.rpl-02__btn--fire .ink {
background: rgba(255,200,150,0.45);
}
.rpl-02__btn--ocean {
background: linear-gradient(135deg, #0077b6, #00b4d8);
box-shadow: 0 8px 32px rgba(0,119,182,0.35);
}
.rpl-02__btn--ocean .ink {
background: rgba(160,230,255,0.4);
}
.rpl-02__btn--emerald {
background: linear-gradient(135deg, #065f46, #10b981);
box-shadow: 0 8px 32px rgba(16,185,129,0.3);
}
.rpl-02__btn--emerald .ink {
background: rgba(160,255,210,0.4);
}
.rpl-02__btn--violet {
background: linear-gradient(135deg, #5b21b6, #8b5cf6);
box-shadow: 0 8px 32px rgba(139,92,246,0.35);
}
.rpl-02__btn--violet .ink {
background: rgba(220,190,255,0.4);
}
.rpl-02__btn--gold {
background: linear-gradient(135deg, #92400e, #f59e0b);
box-shadow: 0 8px 32px rgba(245,158,11,0.3);
}
.rpl-02__btn--gold .ink {
background: rgba(255,245,180,0.45);
}
.rpl-02__btn--rose {
background: linear-gradient(135deg, #9f1239, #fb7185);
box-shadow: 0 8px 32px rgba(251,113,133,0.35);
}
.rpl-02__btn--rose .ink {
background: rgba(255,200,210,0.4);
}
/* Outlined ghost variant */
.rpl-02__btn--ghost {
background: transparent;
border: 1.5px solid rgba(255,255,255,0.25);
color: var(--text);
box-shadow: none;
}
.rpl-02__btn--ghost:hover {
border-color: rgba(255,255,255,0.55);
background: rgba(255,255,255,0.04);
}
.rpl-02__btn--ghost .ink {
background: rgba(255,255,255,0.15);
}
/* Dark surface variant */
.rpl-02__btn--dark {
background: var(--surface);
border: 1.5px solid var(--border);
color: var(--text);
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.rpl-02__btn--dark .ink {
background: rgba(255,255,255,0.12);
}
/* Icon pill */
.rpl-02__icon {
font-size: 1.1rem;
line-height: 1;
}
/* Code snippet */
.rpl-02__code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: min(860px, 100%);
}
.rpl-02__code h3 {
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 14px;
}
.rpl-02__code pre {
font-size: 0.82rem;
line-height: 1.65;
color: rgba(240,240,245,0.75);
font-family: 'Courier New', monospace;
white-space: pre-wrap;
word-break: break-word;
}
.rpl-02__code .kw {
color: #c084fc;
}
.rpl-02__code .str {
color: #86efac;
}
.rpl-02__code .cm {
color: rgba(240,240,245,0.3);
font-style: italic;
}
@media (prefers-reduced-motion: reduce) {
.rpl-02__btn .ink {
animation: none !important;
}
.rpl-02__btn {
transition: none;
}
}document.querySelectorAll('.rpl-02__btn').forEach(btn => {
btn.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const size = Math.max(rect.width, rect.height);
const ink = document.createElement('span');
ink.className = 'ink';
ink.style.cssText = `
left:${x - size/2}px; top:${y - size/2}px;
width:${size}px; height:${size}px;
`;
this.appendChild(ink);
ink.addEventListener('animationend', () => ink.remove(), { once: true });
});
});document.querySelectorAll('.rpl-02__btn').forEach(btn => {
btn.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const size = Math.max(rect.width, rect.height);
const ink = document.createElement('span');
ink.className = 'ink';
ink.style.cssText = `
left:${x - size/2}px; top:${y - size/2}px;
width:${size}px; height:${size}px;
`;
this.appendChild(ink);
ink.addEventListener('animationend', () => ink.remove(), { once: true });
});
});Here's a working CSS Ripple Effect 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: CSS Ripple Button Click Animation
Source: https://codefronts.com/motion/css-ripple-designs/css-ripple-button-click-animation/
A full Material-style ink-ripple button kit in eight color variants — fire, ocean, emerald, violet, gold, rose, ghost and dark-surface — each with a radial ink span injected at the click coordinate that scales to fill and fades. A live code snippet panel explains the technique. Inter on a near-black surface.
## HTML
```html
<div class="rpl-02">
<div class="rpl-02__header">
<h1>Ripple Button Click Animation</h1>
<p>Material-style ink ripple on every button press — pure CSS + minimal JS</p>
</div>
<div class="rpl-02__grid">
<button class="rpl-02__btn rpl-02__btn--fire">
<span class="rpl-02__icon">🔥</span> Get Started
</button>
<button class="rpl-02__btn rpl-02__btn--ocean">
<span class="rpl-02__icon">🌊</span> Dive In
</button>
<button class="rpl-02__btn rpl-02__btn--emerald">
<span class="rpl-02__icon">✓</span> Confirm
</button>
<button class="rpl-02__btn rpl-02__btn--violet">
<span class="rpl-02__icon">✦</span> Upgrade
</button>
<button class="rpl-02__btn rpl-02__btn--gold">
<span class="rpl-02__icon">★</span> Premium
</button>
<button class="rpl-02__btn rpl-02__btn--rose">
<span class="rpl-02__icon">♥</span> Save
</button>
<button class="rpl-02__btn rpl-02__btn--ghost">
Ghost Style
</button>
<button class="rpl-02__btn rpl-02__btn--dark">
Dark Surface
</button>
</div>
<div class="rpl-02__code">
<h3>How it works</h3>
<pre><span class="cm">/* On click, inject a positioned ink div */</span>
<span class="kw">.btn</span> { position: relative; overflow: hidden; }
<span class="kw">.ink</span> {
position: absolute; border-radius: 50%;
animation: <span class="str">ink-expand</span> 0.6s linear forwards;
}
@keyframes <span class="str">ink-expand</span> {
to { transform: scale(4); opacity: 0; }
}</pre>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
.rpl-02,
.rpl-02 *,
.rpl-02 *::before,
.rpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rpl-02 ::selection {
background: #ff6b35;
color: #fff;
}
.rpl-02 {
--bg: #0f0f13;
--surface: #1a1a23;
--border: rgba(255,255,255,0.08);
--text: #f0f0f5;
--muted: rgba(240,240,245,0.45);
font-family: 'Inter', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 56px;
padding: 60px 24px;
color: var(--text);
}
.rpl-02__header {
text-align: center;
}
.rpl-02__header h1 {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.03em;
}
.rpl-02__header p {
margin-top: 10px;
color: var(--muted);
font-weight: 300;
font-size: 0.95rem;
}
.rpl-02__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: min(860px, 100%);
}
/* The core ripple button */
.rpl-02__btn {
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
font-family: 'Inter';
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.01em;
padding: 18px 32px;
border-radius: 12px;
color: #fff;
transition: transform 0.15s, filter 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 60px;
}
.rpl-02__btn:hover {
transform: translateY(-2px);
filter: brightness(1.08);
}
.rpl-02__btn:active {
transform: translateY(1px);
filter: brightness(0.95);
}
/* Ripple ink */
.rpl-02__btn .ink {
position: absolute;
border-radius: 50%;
transform: scale(0);
pointer-events: none;
animation: rpl-02-ink 0.6s linear forwards;
}
@keyframes rpl-02-ink {
to {
transform: scale(4);
opacity: 0;
}
}
/* Variant colors */
.rpl-02__btn--fire {
background: linear-gradient(135deg, #ff4500, #ff6b35);
box-shadow: 0 8px 32px rgba(255,69,0,0.35);
}
.rpl-02__btn--fire .ink {
background: rgba(255,200,150,0.45);
}
.rpl-02__btn--ocean {
background: linear-gradient(135deg, #0077b6, #00b4d8);
box-shadow: 0 8px 32px rgba(0,119,182,0.35);
}
.rpl-02__btn--ocean .ink {
background: rgba(160,230,255,0.4);
}
.rpl-02__btn--emerald {
background: linear-gradient(135deg, #065f46, #10b981);
box-shadow: 0 8px 32px rgba(16,185,129,0.3);
}
.rpl-02__btn--emerald .ink {
background: rgba(160,255,210,0.4);
}
.rpl-02__btn--violet {
background: linear-gradient(135deg, #5b21b6, #8b5cf6);
box-shadow: 0 8px 32px rgba(139,92,246,0.35);
}
.rpl-02__btn--violet .ink {
background: rgba(220,190,255,0.4);
}
.rpl-02__btn--gold {
background: linear-gradient(135deg, #92400e, #f59e0b);
box-shadow: 0 8px 32px rgba(245,158,11,0.3);
}
.rpl-02__btn--gold .ink {
background: rgba(255,245,180,0.45);
}
.rpl-02__btn--rose {
background: linear-gradient(135deg, #9f1239, #fb7185);
box-shadow: 0 8px 32px rgba(251,113,133,0.35);
}
.rpl-02__btn--rose .ink {
background: rgba(255,200,210,0.4);
}
/* Outlined ghost variant */
.rpl-02__btn--ghost {
background: transparent;
border: 1.5px solid rgba(255,255,255,0.25);
color: var(--text);
box-shadow: none;
}
.rpl-02__btn--ghost:hover {
border-color: rgba(255,255,255,0.55);
background: rgba(255,255,255,0.04);
}
.rpl-02__btn--ghost .ink {
background: rgba(255,255,255,0.15);
}
/* Dark surface variant */
.rpl-02__btn--dark {
background: var(--surface);
border: 1.5px solid var(--border);
color: var(--text);
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.rpl-02__btn--dark .ink {
background: rgba(255,255,255,0.12);
}
/* Icon pill */
.rpl-02__icon {
font-size: 1.1rem;
line-height: 1;
}
/* Code snippet */
.rpl-02__code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: min(860px, 100%);
}
.rpl-02__code h3 {
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 14px;
}
.rpl-02__code pre {
font-size: 0.82rem;
line-height: 1.65;
color: rgba(240,240,245,0.75);
font-family: 'Courier New', monospace;
white-space: pre-wrap;
word-break: break-word;
}
.rpl-02__code .kw {
color: #c084fc;
}
.rpl-02__code .str {
color: #86efac;
}
.rpl-02__code .cm {
color: rgba(240,240,245,0.3);
font-style: italic;
}
@media (prefers-reduced-motion: reduce) {
.rpl-02__btn .ink {
animation: none !important;
}
.rpl-02__btn {
transition: none;
}
}
```
## JavaScript
```js
document.querySelectorAll('.rpl-02__btn').forEach(btn => {
btn.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const size = Math.max(rect.width, rect.height);
const ink = document.createElement('span');
ink.className = 'ink';
ink.style.cssText = `
left:${x - size/2}px; top:${y - size/2}px;
width:${size}px; height:${size}px;
`;
this.appendChild(ink);
ink.addEventListener('animationend', () => ink.remove(), { once: true });
});
});
```Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Ripple Effect 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: CSS Ripple Button Click Animation
Source: https://codefronts.com/motion/css-ripple-designs/css-ripple-button-click-animation/
A full Material-style ink-ripple button kit in eight color variants — fire, ocean, emerald, violet, gold, rose, ghost and dark-surface — each with a radial ink span injected at the click coordinate that scales to fill and fades. A live code snippet panel explains the technique. Inter on a near-black surface.
## HTML
```html
<div class="rpl-02">
<div class="rpl-02__header">
<h1>Ripple Button Click Animation</h1>
<p>Material-style ink ripple on every button press — pure CSS + minimal JS</p>
</div>
<div class="rpl-02__grid">
<button class="rpl-02__btn rpl-02__btn--fire">
<span class="rpl-02__icon">🔥</span> Get Started
</button>
<button class="rpl-02__btn rpl-02__btn--ocean">
<span class="rpl-02__icon">🌊</span> Dive In
</button>
<button class="rpl-02__btn rpl-02__btn--emerald">
<span class="rpl-02__icon">✓</span> Confirm
</button>
<button class="rpl-02__btn rpl-02__btn--violet">
<span class="rpl-02__icon">✦</span> Upgrade
</button>
<button class="rpl-02__btn rpl-02__btn--gold">
<span class="rpl-02__icon">★</span> Premium
</button>
<button class="rpl-02__btn rpl-02__btn--rose">
<span class="rpl-02__icon">♥</span> Save
</button>
<button class="rpl-02__btn rpl-02__btn--ghost">
Ghost Style
</button>
<button class="rpl-02__btn rpl-02__btn--dark">
Dark Surface
</button>
</div>
<div class="rpl-02__code">
<h3>How it works</h3>
<pre><span class="cm">/* On click, inject a positioned ink div */</span>
<span class="kw">.btn</span> { position: relative; overflow: hidden; }
<span class="kw">.ink</span> {
position: absolute; border-radius: 50%;
animation: <span class="str">ink-expand</span> 0.6s linear forwards;
}
@keyframes <span class="str">ink-expand</span> {
to { transform: scale(4); opacity: 0; }
}</pre>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
.rpl-02,
.rpl-02 *,
.rpl-02 *::before,
.rpl-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rpl-02 ::selection {
background: #ff6b35;
color: #fff;
}
.rpl-02 {
--bg: #0f0f13;
--surface: #1a1a23;
--border: rgba(255,255,255,0.08);
--text: #f0f0f5;
--muted: rgba(240,240,245,0.45);
font-family: 'Inter', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 56px;
padding: 60px 24px;
color: var(--text);
}
.rpl-02__header {
text-align: center;
}
.rpl-02__header h1 {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.03em;
}
.rpl-02__header p {
margin-top: 10px;
color: var(--muted);
font-weight: 300;
font-size: 0.95rem;
}
.rpl-02__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: min(860px, 100%);
}
/* The core ripple button */
.rpl-02__btn {
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
font-family: 'Inter';
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.01em;
padding: 18px 32px;
border-radius: 12px;
color: #fff;
transition: transform 0.15s, filter 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 60px;
}
.rpl-02__btn:hover {
transform: translateY(-2px);
filter: brightness(1.08);
}
.rpl-02__btn:active {
transform: translateY(1px);
filter: brightness(0.95);
}
/* Ripple ink */
.rpl-02__btn .ink {
position: absolute;
border-radius: 50%;
transform: scale(0);
pointer-events: none;
animation: rpl-02-ink 0.6s linear forwards;
}
@keyframes rpl-02-ink {
to {
transform: scale(4);
opacity: 0;
}
}
/* Variant colors */
.rpl-02__btn--fire {
background: linear-gradient(135deg, #ff4500, #ff6b35);
box-shadow: 0 8px 32px rgba(255,69,0,0.35);
}
.rpl-02__btn--fire .ink {
background: rgba(255,200,150,0.45);
}
.rpl-02__btn--ocean {
background: linear-gradient(135deg, #0077b6, #00b4d8);
box-shadow: 0 8px 32px rgba(0,119,182,0.35);
}
.rpl-02__btn--ocean .ink {
background: rgba(160,230,255,0.4);
}
.rpl-02__btn--emerald {
background: linear-gradient(135deg, #065f46, #10b981);
box-shadow: 0 8px 32px rgba(16,185,129,0.3);
}
.rpl-02__btn--emerald .ink {
background: rgba(160,255,210,0.4);
}
.rpl-02__btn--violet {
background: linear-gradient(135deg, #5b21b6, #8b5cf6);
box-shadow: 0 8px 32px rgba(139,92,246,0.35);
}
.rpl-02__btn--violet .ink {
background: rgba(220,190,255,0.4);
}
.rpl-02__btn--gold {
background: linear-gradient(135deg, #92400e, #f59e0b);
box-shadow: 0 8px 32px rgba(245,158,11,0.3);
}
.rpl-02__btn--gold .ink {
background: rgba(255,245,180,0.45);
}
.rpl-02__btn--rose {
background: linear-gradient(135deg, #9f1239, #fb7185);
box-shadow: 0 8px 32px rgba(251,113,133,0.35);
}
.rpl-02__btn--rose .ink {
background: rgba(255,200,210,0.4);
}
/* Outlined ghost variant */
.rpl-02__btn--ghost {
background: transparent;
border: 1.5px solid rgba(255,255,255,0.25);
color: var(--text);
box-shadow: none;
}
.rpl-02__btn--ghost:hover {
border-color: rgba(255,255,255,0.55);
background: rgba(255,255,255,0.04);
}
.rpl-02__btn--ghost .ink {
background: rgba(255,255,255,0.15);
}
/* Dark surface variant */
.rpl-02__btn--dark {
background: var(--surface);
border: 1.5px solid var(--border);
color: var(--text);
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.rpl-02__btn--dark .ink {
background: rgba(255,255,255,0.12);
}
/* Icon pill */
.rpl-02__icon {
font-size: 1.1rem;
line-height: 1;
}
/* Code snippet */
.rpl-02__code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: min(860px, 100%);
}
.rpl-02__code h3 {
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 14px;
}
.rpl-02__code pre {
font-size: 0.82rem;
line-height: 1.65;
color: rgba(240,240,245,0.75);
font-family: 'Courier New', monospace;
white-space: pre-wrap;
word-break: break-word;
}
.rpl-02__code .kw {
color: #c084fc;
}
.rpl-02__code .str {
color: #86efac;
}
.rpl-02__code .cm {
color: rgba(240,240,245,0.3);
font-style: italic;
}
@media (prefers-reduced-motion: reduce) {
.rpl-02__btn .ink {
animation: none !important;
}
.rpl-02__btn {
transition: none;
}
}
```
## JavaScript
```js
document.querySelectorAll('.rpl-02__btn').forEach(btn => {
btn.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const size = Math.max(rect.width, rect.height);
const ink = document.createElement('span');
ink.className = 'ink';
ink.style.cssText = `
left:${x - size/2}px; top:${y - size/2}px;
width:${size}px; height:${size}px;
`;
this.appendChild(ink);
ink.addEventListener('animationend', () => ink.remove(), { once: true });
});
});
```