20 CSS Animated Buttons03 / 20

Pure CSSMIT licensed

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

Live Demo
Try it

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>
.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;
  }
}
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 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/left percentages on ::after — e.g. top: 80%; left: 10% for a bottom-left burst.
  • Control ripple speed by editing the animation-duration on .ab-03__btn:active::after (default .6s; try .4s for snappy feel).
  • Change the ripple colour by updating the background on .ab-03__btn::after (default rgba(255,255,255,.45)).
  • Add a fill-colour variant by appending a second keyframe that changes the button background on 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, :active only fires if the element has a cursor: pointer or a touch event listener — add cursor:pointer to the button to ensure activation on mobile.
  • If the button has border-radius > 50% (a pill shape), the escaping corners of the growing ::after circle must be clipped with overflow: hidden on the button.

Browser support

ChromeSafariFirefoxEdge
26+7+16+26+

The :active pseudo-class and CSS animations are universally supported across all modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…