20 CSS Animated Buttons03 / 20
CSS Pure CSS Ripple Effect Button
A Material Design-inspired ripple effect on button click, achieved entirely with CSS pseudo-elements and the :active state — no JavaScript required.
Published
The code
<div class="ab-03">
<p class="ab-03__label">Click or tap to trigger</p>
<div class="ab-03__row">
<button class="ab-03__btn ab-03__btn--violet">Get Started</button>
<button class="ab-03__btn ab-03__btn--teal">Learn More</button>
<button class="ab-03__btn ab-03__btn--rose">Subscribe</button>
</div>
</div><div class="ab-03">
<p class="ab-03__label">Click or tap to trigger</p>
<div class="ab-03__row">
<button class="ab-03__btn ab-03__btn--violet">Get Started</button>
<button class="ab-03__btn ab-03__btn--teal">Learn More</button>
<button class="ab-03__btn ab-03__btn--rose">Subscribe</button>
</div>
</div>.ab-03,
.ab-03 *,
.ab-03 *::before,
.ab-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-03 ::selection {
background: #7c3aed;
color: #fff;
}
.ab-03 {
font-family: system-ui,sans-serif;
background: #0f0c1a;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-03__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #a78bfa;
opacity: .6;
}
.ab-03__row {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-03__btn {
position: relative;
overflow: hidden;
padding: .85rem 2rem;
font-size: .95rem;
font-weight: 700;
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
letter-spacing: .03em;
transition: filter .2s;
}
.ab-03__btn:hover {
filter: brightness(1.1);
}
.ab-03__btn:active {
filter: brightness(.9);
}
.ab-03__btn--violet {
background: #7c3aed;
}
.ab-03__btn--teal {
background: #0d9488;
}
.ab-03__btn--rose {
background: #e11d48;
}
.ab-03__btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255,255,255,.4);
border-radius: 50%;
transform: translate(-50%,-50%);
pointer-events: none;
opacity: 0;
}
.ab-03__btn:active::after {
animation: ab-03-ripple .6s linear forwards;
}
@keyframes ab-03-ripple {
0% {
width: 0;
height: 0;
opacity: .4;
}
80% {
width: 400%;
height: 400%;
opacity: .1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ab-03__btn:active::after {
animation: none;
}
}.ab-03,
.ab-03 *,
.ab-03 *::before,
.ab-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-03 ::selection {
background: #7c3aed;
color: #fff;
}
.ab-03 {
font-family: system-ui,sans-serif;
background: #0f0c1a;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-03__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #a78bfa;
opacity: .6;
}
.ab-03__row {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-03__btn {
position: relative;
overflow: hidden;
padding: .85rem 2rem;
font-size: .95rem;
font-weight: 700;
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
letter-spacing: .03em;
transition: filter .2s;
}
.ab-03__btn:hover {
filter: brightness(1.1);
}
.ab-03__btn:active {
filter: brightness(.9);
}
.ab-03__btn--violet {
background: #7c3aed;
}
.ab-03__btn--teal {
background: #0d9488;
}
.ab-03__btn--rose {
background: #e11d48;
}
.ab-03__btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255,255,255,.4);
border-radius: 50%;
transform: translate(-50%,-50%);
pointer-events: none;
opacity: 0;
}
.ab-03__btn:active::after {
animation: ab-03-ripple .6s linear forwards;
}
@keyframes ab-03-ripple {
0% {
width: 0;
height: 0;
opacity: .4;
}
80% {
width: 400%;
height: 400%;
opacity: .1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ab-03__btn:active::after {
animation: none;
}
}Here's a working CSS Animated Button 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 Pure CSS Ripple Effect Button
Source: https://codefronts.com/motion/css-animated-buttons/css-pure-css-ripple-effect-button/
A Material Design-inspired ripple effect on button click, achieved entirely with CSS pseudo-elements and the :active state — no JavaScript required.
## HTML
```html
<div class="ab-03">
<p class="ab-03__label">Click or tap to trigger</p>
<div class="ab-03__row">
<button class="ab-03__btn ab-03__btn--violet">Get Started</button>
<button class="ab-03__btn ab-03__btn--teal">Learn More</button>
<button class="ab-03__btn ab-03__btn--rose">Subscribe</button>
</div>
</div>
```
## CSS
```css
.ab-03,
.ab-03 *,
.ab-03 *::before,
.ab-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-03 ::selection {
background: #7c3aed;
color: #fff;
}
.ab-03 {
font-family: system-ui,sans-serif;
background: #0f0c1a;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-03__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #a78bfa;
opacity: .6;
}
.ab-03__row {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-03__btn {
position: relative;
overflow: hidden;
padding: .85rem 2rem;
font-size: .95rem;
font-weight: 700;
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
letter-spacing: .03em;
transition: filter .2s;
}
.ab-03__btn:hover {
filter: brightness(1.1);
}
.ab-03__btn:active {
filter: brightness(.9);
}
.ab-03__btn--violet {
background: #7c3aed;
}
.ab-03__btn--teal {
background: #0d9488;
}
.ab-03__btn--rose {
background: #e11d48;
}
.ab-03__btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255,255,255,.4);
border-radius: 50%;
transform: translate(-50%,-50%);
pointer-events: none;
opacity: 0;
}
.ab-03__btn:active::after {
animation: ab-03-ripple .6s linear forwards;
}
@keyframes ab-03-ripple {
0% {
width: 0;
height: 0;
opacity: .4;
}
80% {
width: 400%;
height: 400%;
opacity: .1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ab-03__btn:active::after {
animation: none;
}
}
```Here's a working CSS Animated Button 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 Pure CSS Ripple Effect Button
Source: https://codefronts.com/motion/css-animated-buttons/css-pure-css-ripple-effect-button/
A Material Design-inspired ripple effect on button click, achieved entirely with CSS pseudo-elements and the :active state — no JavaScript required.
## HTML
```html
<div class="ab-03">
<p class="ab-03__label">Click or tap to trigger</p>
<div class="ab-03__row">
<button class="ab-03__btn ab-03__btn--violet">Get Started</button>
<button class="ab-03__btn ab-03__btn--teal">Learn More</button>
<button class="ab-03__btn ab-03__btn--rose">Subscribe</button>
</div>
</div>
```
## CSS
```css
.ab-03,
.ab-03 *,
.ab-03 *::before,
.ab-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-03 ::selection {
background: #7c3aed;
color: #fff;
}
.ab-03 {
font-family: system-ui,sans-serif;
background: #0f0c1a;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-03__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #a78bfa;
opacity: .6;
}
.ab-03__row {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-03__btn {
position: relative;
overflow: hidden;
padding: .85rem 2rem;
font-size: .95rem;
font-weight: 700;
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
letter-spacing: .03em;
transition: filter .2s;
}
.ab-03__btn:hover {
filter: brightness(1.1);
}
.ab-03__btn:active {
filter: brightness(.9);
}
.ab-03__btn--violet {
background: #7c3aed;
}
.ab-03__btn--teal {
background: #0d9488;
}
.ab-03__btn--rose {
background: #e11d48;
}
.ab-03__btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255,255,255,.4);
border-radius: 50%;
transform: translate(-50%,-50%);
pointer-events: none;
opacity: 0;
}
.ab-03__btn:active::after {
animation: ab-03-ripple .6s linear forwards;
}
@keyframes ab-03-ripple {
0% {
width: 0;
height: 0;
opacity: .4;
}
80% {
width: 400%;
height: 400%;
opacity: .1;
}
100% {
width: 500%;
height: 500%;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ab-03__btn:active::after {
animation: none;
}
}
```How this works
The ripple lives in the ::after pseudo-element, which starts as a small circle at the button centre using border-radius: 50% and width/height: 0. On :active, a @keyframes ab-03-ripple animation scales the circle to 400% of the button width while fading opacity from 0.35 to 0 — the classic expanding ring illusion. Centering the pseudo-element with top: 50%; left: 50%; transform: translate(-50%,-50%) keeps the ripple origin consistent regardless of button dimensions.
Because the animation only fires on :active, it naturally resets when the user releases the pointer. overflow: hidden on the parent clips the expanding disk, while pointer-events: none on the pseudo-element prevents it from interfering with the click target. The entire effect uses only transform, opacity, and width/height inside the keyframe, with only the transform path on the compositor — width/height changes trigger layout but are brief and isolated to the pseudo-element.
Make it yours
- Reposition the ripple origin to a corner by changing the
top/leftpercentages on::after— e.g.top: 80%; left: 10%for a bottom-left burst. - Control ripple speed by editing the
animation-durationon.ab-03__btn:active::after(default.6s; try.4sfor snappy feel). - Change the ripple colour by updating the
backgroundon.ab-03__btn::after(defaultrgba(255,255,255,.45)). - Add a fill-colour variant by appending a second keyframe that changes the button
backgroundon active, creating a ripple-plus-fill combo. - Give dark-coloured buttons a dark ripple by switching the pseudo-element background to
rgba(0,0,0,.12)so it reads on light fill colours.
Gotchas — read before shipping
- The pure-CSS ripple always originates from the element centre — it cannot track where the user clicked. Use a JS-powered version if accurate origin matters for the design.
- On iOS Safari,
:activeonly fires if the element has acursor: pointeror a touch event listener — addcursor:pointerto the button to ensure activation on mobile. - If the button has
border-radius > 50%(a pill shape), the escaping corners of the growing::aftercircle must be clipped withoverflow: hiddenon the button.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 26+ | 7+ | 16+ | 26+ |
The :active pseudo-class and CSS animations are universally supported across all modern browsers.