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.
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> <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}
} .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);
});
})(); (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
setTimeoutreset delay from2500to4000ms. - Add an error state by creating a
.is-errorclass 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-sizefrom0% 100%to100% 100%on a linear-gradient prior to the class swap. - Disable the auto-reset by removing the
setTimeoutand letting the success state persist until page reload — useful for real form submissions.
Watch out for
- Transitioning
widthfrom a percentage to a fixed pixel value causes layout reflow — keep the button in aflexcontainer with a fixed reserved width so surrounding elements don't shift during the collapse. - The SVG
stroke-dashoffsetanimation requires the exactstroke-dasharrayvalue to match the path length — if you change the checkmark SVG path, recalculatepathLengthwith JSgetTotalLength(). - On Safari,
border-radiustransition from50pxto50%during the width collapse can momentarily look rectangular — force GPU promotion withtransform: translateZ(0)on the button to prevent this.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 10.1+ | 44+ | 49+ |
SVG stroke-dashoffset animation and width transitions are universally supported in modern browsers.