20 CSS Animated Buttons 12 / 20

CSS Submit Button Success State

A form submit button that morphs into a checkmark success state with a width collapse animation and icon draw, then resets — all styled with CSS transitions and driven by a JS class toggle.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ab-12">
  <div class="ab-12__card">
    <h2 class="ab-12__title">Contact Us</h2>
    <p class="ab-12__sub">We'll get back to you within 24 hours.</p>
    <div class="ab-12__field"><input class="ab-12__input" type="text" placeholder="Your name" /></div>
    <div class="ab-12__field"><input class="ab-12__input" type="email" placeholder="Email address" /></div>
    <button class="ab-12__btn" id="ab-12-btn">
      <span class="ab-12__text" id="ab-12-text">Send Message</span>
      <svg class="ab-12__check" id="ab-12-check" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <polyline points="20 6 9 17 4 12" pathLength="30"></polyline>
      </svg>
    </button>
  </div>
</div>
.ab-12,.ab-12 *,.ab-12 *::before,.ab-12 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-12 ::selection{background:#10b981;color:#fff}
.ab-12{
  --success:#10b981;
  --primary:#4f46e5;
  font-family:system-ui,sans-serif;
  background:#f8fafc;
  min-height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:2rem;
}
.ab-12__card{
  background:#fff;
  border-radius:16px;
  box-shadow:0 4px 24px rgba(0,0,0,.08);
  padding:2rem 2rem 2rem;
  width:100%;max-width:380px;
}
.ab-12__title{font-size:1.4rem;font-weight:800;color:#1e293b;margin-bottom:.4rem}
.ab-12__sub{font-size:.88rem;color:#64748b;margin-bottom:1.5rem;line-height:1.5}
.ab-12__field{margin-bottom:.9rem}
.ab-12__input{
  width:100%;
  padding:.7rem 1rem;
  border:1.5px solid #e2e8f0;
  border-radius:8px;
  font-size:.9rem;
  color:#1e293b;
  outline:none;
  transition:border-color .2s;
  background:#f8fafc;
}
.ab-12__input:focus{border-color:var(--primary)}
.ab-12__btn{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  height:48px;
  padding:0 1.5rem;
  font-size:1rem;
  font-weight:700;
  color:#fff;
  background:var(--primary);
  border:none;
  border-radius:50px;
  cursor:pointer;
  outline:none;
  overflow:hidden;
  transition:width .4s cubic-bezier(.4,0,.2,1),background .4s,box-shadow .3s;
  box-shadow:0 4px 16px rgba(79,70,229,.3);
  margin-top:.25rem;
  will-change:width;
}
.ab-12__btn:hover:not(.is-success){filter:brightness(1.08)}
.ab-12__btn.is-success{
  width:48px;
  background:var(--success);
  box-shadow:0 4px 16px rgba(16,185,129,.35);
}
.ab-12__text{
  transition:opacity .2s,transform .2s;
  white-space:nowrap;
}
.ab-12__btn.is-success .ab-12__text{opacity:0;transform:scale(.6);position:absolute}
.ab-12__check{
  position:absolute;
  width:24px;height:24px;
  opacity:0;
  transform:scale(.6);
  transition:opacity .2s .35s,transform .3s .35s;
}
.ab-12__check polyline{
  stroke-dasharray:30;
  stroke-dashoffset:30;
}
.ab-12__btn.is-success .ab-12__check{opacity:1;transform:scale(1)}
.ab-12__btn.is-success .ab-12__check polyline{
  animation:ab-12-draw .4s ease .4s forwards;
}
@keyframes ab-12-draw{
  to{stroke-dashoffset:0}
}
@media(prefers-reduced-motion:reduce){
  .ab-12__btn,.ab-12__check,.ab-12__text{transition:none}
  .ab-12__btn.is-success .ab-12__check polyline{animation:none;stroke-dashoffset:0}
}
(function(){
  var btn=document.getElementById('ab-12-btn');
  if(!btn)return;
  var timer=null;
  btn.addEventListener('click',function(){
    if(btn.classList.contains('is-success'))return;
    btn.classList.add('is-success');
    btn.disabled=true;
    clearTimeout(timer);
    timer=setTimeout(function(){
      btn.classList.remove('is-success');
      btn.disabled=false;
    },2500);
  });
})();

How this works

The button holds two overlapping content layers: the default label text and a hidden SVG checkmark. In the resting state the SVG is opacity: 0; transform: scale(0). On submit, JS adds the class .is-success which transitions the button width to a circle (matching its height), fades the label text out, and fades + scales the checkmark SVG in. The width collapse uses transition: width .4s cubic-bezier(.4,0,.2,1); the button already has border-radius: 50px so the collapsed pill naturally becomes a circle.

The checkmark SVG uses stroke-dasharray and stroke-dashoffset animation to draw the tick path on entry — a @keyframes ab-12-draw keyframe reduces offset from 30 to 0 over .4s with a .3s delay so it fires after the pill collapse completes. JS resets the class after 2.5 s for demo looping, simulating the button returning to ready state.

Customize

  • Change the success colour by updating --success (default #10b981) — transitions on background and box-shadow both inherit this variable.
  • Lengthen the success display window by increasing the setTimeout reset delay from 2500 to 4000 ms.
  • Add an error state by creating a .is-error class that transitions the button to red and shows an X icon using the same pattern.
  • Show a progress fill before the success state by animating a background-size from 0% 100% to 100% 100% on a linear-gradient prior to the class swap.
  • Disable the auto-reset by removing the setTimeout and letting the success state persist until page reload — useful for real form submissions.

Watch out for

  • Transitioning width from a percentage to a fixed pixel value causes layout reflow — keep the button in a flex container with a fixed reserved width so surrounding elements don't shift during the collapse.
  • The SVG stroke-dashoffset animation requires the exact stroke-dasharray value to match the path length — if you change the checkmark SVG path, recalculate pathLength with JS getTotalLength().
  • On Safari, border-radius transition from 50px to 50% during the width collapse can momentarily look rectangular — force GPU promotion with transform: translateZ(0) on the button to prevent this.

Browser support

ChromeSafariFirefoxEdge
49+ 10.1+ 44+ 49+

SVG stroke-dashoffset animation and width transitions are universally supported in modern browsers.

Search CodeFronts

Loading…