How do you make a button animation that helps people click?

An animated button uses motion to show that it can be pressed and to confirm the click. The reliable techniques are CSS transitions on transform, box-shadow and background-position, pseudo-elements for shine and border effects, and a few lines of JavaScript for ripples or a magnetic pull toward the pointer.

How to choose, performance and accessibility
  • For a main call to action, prefer one clear cue, such as Glow Pulse, Shine Sweep or Gradient Shift, over several at once; save Magnetic Button or Liquid Fill for creative sites.
  • Keep the animation short (roughly 150 to 300 ms), give keyboard users the same feedback with :focus-visible, and never convey a state by motion alone.
  • Gradient Shift and Ripple Effect are free with their full code; the other buttons on this page come with Premium.

Gradient Shift

The gradient shifts on hover creating a smooth motion effect.

CSS Hover Gradient
Effect page →
View source code
HTML
<div class="demo-preview">
  <button class="demo-btn btn-gradient-shift">Gradient Shift</button>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.demo-btn {
  padding: 16px 32px;
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  font-family: inherit;
  position: relative;
  overflow: hidden;
}

.btn-gradient-shift {
  background: linear-gradient(135deg, #6366f1, #8b5cf6, #d946ef);
  background-size: 200% 200%;
  color: white;
  transition: all 0.5s ease;
}

.btn-gradient-shift:hover {
  background-position: 100% 100%;
  transform: translateY(-3px);
  box-shadow: 0 15px 35px rgba(99, 102, 241, 0.4);
}
JavaScript
/* Pure CSS — no JavaScript required */

Ripple Effect

Click ripple effect inspired by Material Design.

JS Click Animation
Effect page →
View source code
HTML
<div class="demo-preview">
  <button class="demo-btn btn-ripple" onclick="createRipple(event)">Ripple Effect</button>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.demo-btn {
  padding: 16px 32px;
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  font-family: inherit;
  position: relative;
  overflow: hidden;
}

.btn-ripple {
  background: #6366f1;
  color: white;
  transition: all 0.3s ease;
}

.btn-ripple .ripple-circle {
  position: absolute;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
  transform: scale(0);
  animation: ripple 0.6s linear;
  pointer-events: none;
}

@keyframes ripple {
  to {
    transform: scale(4);
    opacity: 0;
  }
}
JavaScript
(function() {
  // Ripple Effect
  function createRipple(e) {
    const btn = e.currentTarget;
    const rect = btn.getBoundingClientRect();
    const circle = document.createElement('span');
    const diameter = Math.max(btn.clientWidth, btn.clientHeight);
    const radius = diameter / 2;

    circle.style.width = circle.style.height = `${diameter}px`;
    circle.style.left = `${e.clientX - rect.left - radius}px`;
    circle.style.top = `${e.clientY - rect.top - radius}px`;
    circle.classList.add('ripple-circle');

    const ripple = btn.querySelector('.ripple-circle');
    if (ripple) ripple.remove();

    circle.addEventListener('animationend', () => circle.remove());
    btn.appendChild(circle);
  }
  window.createRipple = createRipple;
})();

Magnetic Button

The button follows the cursor with a magnetic attraction effect.

JS Mouse Interactive

Border Draw

Borders draw themselves progressively on hover.

CSS Hover Border

Shine Sweep

A light reflection sweeps across the button on hover.

CSS Hover Shine

Glow Pulse

Glowing halo that pulses continuously to attract attention.

CSS Animation Glow

3D Press

Physical button effect with depth and click feedback.

CSS 3D Active

Liquid Fill

Progressive fill like a rising liquid.

CSS Hover Fill

Icon Slide

Icon that slides in on hover.

CSS Hover Icon

Neon Glow

Cyberpunk neon effect with intense glow on hover.

CSS Hover Neon

Rotating Border

Gradient border that rotates continuously.

CSS Animation Border

Morph

Shape transformation with elastic effect.

CSS Hover Transform

Particle Trail

Colorful particle trail that follows the cursor on hover.

JS Mouse Particles

Split Text

Text splits letter by letter on hover then reforms.

JS Hover Text

Fill Slide Up

Bottom-to-top fill with ease-quart curve. Inspired by Daily Delights.

CSS Hover Fill

Press Feedback

Tactile micro-interaction with click compression for optimal user feedback.

CSS Active Micro

Bezier Elastic

Animation with elastic cubic-bezier curve for a natural bounce effect.

CSS Bezier Elastic

Slide Reveal

Text slides up to reveal new colored text.

CSS Text Reveal

CTA Gradient Border

Animated gradient border perfect for premium call-to-action. Inspired by CTA Gallery.

CSS CTA Gradient

Neon Pulse

Pulsing neon box-shadow with continuous animation for an intense cyberpunk effect.

CSS Animation Neon

Gummy Elastic

Elastic click effect with cubic-bezier curve for a natural and fun bounce.

CSS Hover Elastic

Glitch Effect

Glitch effect with pseudo-elements and chromatic shift in retro gaming style.

CSS Animation Glitch

RGB Split

RGB channel separation on hover for a chromatic aberration effect.

CSS Hover RGB

3D Depth Press

Depth effect with drop shadow and physical click feedback.

CSS 3D Active

Neumorphic

Soft UI/neumorphism effect with gentle shadows and click inversion.

CSS Soft UI Active

Gradient Border Animated

Rotating gradient border with continuous animation using CSS @property.

CSS Animation Border

Slide Reveal Icon

Icon that appears progressively on hover with smooth expansion.

CSS Hover Icon
0

Studio Pro Headphones

149 €

Magnetic Add to Cart

Add-to-cart micro-interaction: magnetic button, particle burst, item flies in a curve to the cart with an animated counter. The satisfying click that drives purchases.

💡 Perfect for: e-commerce, product page, Black Friday, cart, any "add / save / like" CTA.

Vanilla JS Micro-interaction E-commerce Free
View source code
HTML
<div class="demo-preview">
  <div class="atc-scene" data-atc data-icon="🎧">
    <div class="atc-cart-wrap">
      <span class="atc-cart" aria-hidden="true">🛒</span>
      <span class="atc-count" data-atc-count aria-label="Items in cart">0</span>
    </div>
    <div class="atc-product">
      <div class="atc-thumb" aria-hidden="true">🎧</div>
      <div class="atc-info">
        <h3>Studio Pro Headphones</h3>
        <span class="atc-price">149 €</span>
        <div><button type="button" class="atc-btn" data-atc-btn>Add to cart</button></div>
      </div>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 200px;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.atc-scene {
  position: relative;
  width: 100%;
  min-height: 240px;
  border-radius: 14px;
  overflow: hidden;
  isolation: isolate;
  background: radial-gradient(circle at 70% 20%, #1c1330, #0b0a14 75%);
  padding: 22px;
  box-sizing: border-box;
}

.atc-cart {
  position: absolute;
  top: 16px;
  right: 18px;
  font-size: 26px;
  line-height: 1;
}

.atc-count {
  position: absolute;
  top: -8px;
  right: -10px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  box-sizing: border-box;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  border-radius: 999px;
  display: grid;
  place-items: center;
}

.atc-cart-wrap {
  position: absolute;
  top: 16px;
  right: 18px;
}

.atc-product {
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: 360px;
  margin-top: 34px;
}

.atc-thumb {
  width: 78px;
  height: 78px;
  border-radius: 14px;
  background: linear-gradient(135deg, #6366f1, #d946ef);
  display: grid;
  place-items: center;
  font-size: 38px;
  flex: none;
}

.atc-info h3 {
  margin: 0 0 2px;
  font-size: 16px;
  color: #fff;
}

.atc-info .atc-price {
  color: #a78bfa;
  font-weight: 700;
}

.atc-btn {
  margin-top: 14px;
  border: 0;
  cursor: pointer;
  background: linear-gradient(135deg, #f59e0b, #ef4444);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  padding: 11px 20px;
  border-radius: 10px;
  transition: transform .12s, box-shadow .2s;
  will-change: transform;
}

.atc-btn:hover {
  box-shadow: 0 8px 24px rgba(245, 158, 11, .35);
}

.atc-btn:active {
  transform: scale(.96);
}

.atc-particle {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #fbbf24;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: atc-burst .6s ease-out forwards;
  z-index: 4;
}

.atc-fly {
  position: absolute;
  font-size: 30px;
  pointer-events: none;
  z-index: 5;
  transform: translate(-50%, -50%);
}

.atc-pop {
  animation: atc-pop .32s ease;
}

.atc-bump {
  animation: atc-bump .4s ease;
}

@media (prefers-reduced-motion: reduce) {
  .atc-btn {
    transition: none;
  }
}

@keyframes atc-burst {
  to {
    transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(.2);
    opacity: 0;
  }
}

@keyframes atc-pop {
  0% {
    transform: scale(1);
  }

  40% {
    transform: scale(1.6);
  }

  100% {
    transform: scale(1);
  }
}

@keyframes atc-bump {

  0%,
  100% {
    transform: translateY(0);
  }

  30% {
    transform: translateY(-7px) rotate(-8deg);
  }
}
JavaScript
(function() {
  (function() {
    var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    document.querySelectorAll('[data-atc]').forEach(function(scene) {
      var btn = scene.querySelector('[data-atc-btn]');
      var cart = scene.querySelector('.atc-cart');
      var countEl = scene.querySelector('[data-atc-count]');
      var icon = scene.dataset.icon || '🛍️';
      var n = 0;

      if (!reduceMotion) {
        btn.addEventListener('pointermove', function(e) {
          var r = btn.getBoundingClientRect();
          var mx = e.clientX - (r.left + r.width / 2),
            my = e.clientY - (r.top + r.height / 2);
          btn.style.transform = 'translate(' + (mx * 0.22) + 'px,' + (my * 0.35) + 'px)';
        });
        btn.addEventListener('pointerleave', function() {
          btn.style.transform = '';
        });
      }

      function bump() {
        n++;
        countEl.textContent = n;
        countEl.classList.remove('atc-pop');
        void countEl.offsetWidth;
        countEl.classList.add('atc-pop');
        cart.classList.remove('atc-bump');
        void cart.offsetWidth;
        cart.classList.add('atc-bump');
      }

      btn.addEventListener('click', function() {
        if (reduceMotion) {
          bump();
          return;
        }
        var br = btn.getBoundingClientRect(),
          cr = cart.getBoundingClientRect(),
          sr = scene.getBoundingClientRect();
        var x0 = br.left + br.width / 2 - sr.left,
          y0 = br.top + br.height / 2 - sr.top;
        var x1 = cr.left + cr.width / 2 - sr.left,
          y1 = cr.top + cr.height / 2 - sr.top;

        for (var i = 0; i < 14; i++) {
          var p = document.createElement('span');
          p.className = 'atc-particle';
          p.style.left = x0 + 'px';
          p.style.top = y0 + 'px';
          var a = Math.random() * 6.2832,
            d = 24 + Math.random() * 46;
          p.style.setProperty('--dx', Math.cos(a) * d + 'px');
          p.style.setProperty('--dy', Math.sin(a) * d + 'px');
          scene.appendChild(p);
          (function(el) {
            setTimeout(function() {
              el.remove();
            }, 650);
          })(p);
        }

        var chip = document.createElement('span');
        chip.className = 'atc-fly';
        chip.textContent = icon;
        chip.style.left = x0 + 'px';
        chip.style.top = y0 + 'px';
        scene.appendChild(chip);
        var t0 = performance.now(),
          dur = 620,
          cx = (x0 + x1) / 2,
          cy = Math.min(y0, y1) - 70;
        (function anim(t) {
          var k = Math.min((t - t0) / dur, 1);
          var e = k < .5 ? 2 * k * k : 1 - Math.pow(-2 * k + 2, 2) / 2,
            m = 1 - e;
          var x = m * m * x0 + 2 * m * e * cx + e * e * x1;
          var y = m * m * y0 + 2 * m * e * cy + e * e * y1;
          chip.style.transform = 'translate(-50%,-50%) translate(' + (x - x0) + 'px,' + (y - y0) + 'px) scale(' + (1 - 0.55 * e) + ')';
          chip.style.opacity = String(1 - 0.25 * e);
          if (k < 1) requestAnimationFrame(anim);
          else {
            chip.remove();
            bump();
          }
        })(t0);
      });
    });
  })();
})();

Frequently Asked Questions

How to animate a button on hover in CSS? +

Use the CSS transition property on the properties you want to animate (background, transform, box-shadow), then define the final state in the :hover selector. For example: transition: all 0.3s ease; on the button, then transform: translateY(-2px) on :hover.

What is the best hover effect for a CTA button? +

The most effective effects for a CTA are gradient shift (color movement), glow (halo around the button) and scale-up (slight enlargement). They catch the eye without distracting. Avoid animations that are too long (over 0.4s) which slow down the user.

How to create a neon button in CSS? +

The neon effect uses box-shadow with multiple color layers. Apply 2-3 box-shadows with increasing sizes and a vivid color (e.g. #6366f1). Add text-shadow to make the text glow. Animate the intensity on hover for a pulse effect.

Do button animations impact performance? +

CSS animations using transform and opacity are GPU-optimized and do not impact performance. Avoid animating width, height or margin which trigger layout recalculation. Use will-change: transform on animated buttons to prepare the browser.

How to make an animated button accessible? +

Always add an aria-label if the text is not descriptive. Maintain a minimum contrast ratio of 4.5:1. Support prefers-reduced-motion with a media query that disables animations. Make sure the button is keyboard-navigable with a visible focus.