20 CSS Animated Buttons 14 / 20
CSS Hamburger To Close Button
The classic three-line hamburger menu icon morphs smoothly into an × close icon via CSS transform transitions — no SVG swaps, just rotating and fading three spans.
The code
<div class="ab-14">
<div class="ab-14__nav">
<span class="ab-14__brand">MyApp</span>
<button class="ab-14__btn" id="ab-14-btn" aria-label="Toggle navigation" aria-expanded="false">
<span class="ab-14__bar"></span>
<span class="ab-14__bar"></span>
<span class="ab-14__bar"></span>
</button>
</div>
<div class="ab-14__menu" id="ab-14-menu" aria-hidden="true">
<a class="ab-14__link" href="#">Home</a>
<a class="ab-14__link" href="#">Features</a>
<a class="ab-14__link" href="#">Pricing</a>
<a class="ab-14__link" href="#">Docs</a>
</div>
</div> <div class="ab-14">
<div class="ab-14__nav">
<span class="ab-14__brand">MyApp</span>
<button class="ab-14__btn" id="ab-14-btn" aria-label="Toggle navigation" aria-expanded="false">
<span class="ab-14__bar"></span>
<span class="ab-14__bar"></span>
<span class="ab-14__bar"></span>
</button>
</div>
<div class="ab-14__menu" id="ab-14-menu" aria-hidden="true">
<a class="ab-14__link" href="#">Home</a>
<a class="ab-14__link" href="#">Features</a>
<a class="ab-14__link" href="#">Pricing</a>
<a class="ab-14__link" href="#">Docs</a>
</div>
</div>.ab-14,.ab-14 *,.ab-14 *::before,.ab-14 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-14 ::selection{background:#7c3aed;color:#fff}
.ab-14{
font-family:system-ui,sans-serif;
background:#0f172a;
min-height:100vh;
}
.ab-14__nav{
display:flex;
align-items:center;
justify-content:space-between;
padding:1rem 1.5rem;
border-bottom:1px solid rgba(255,255,255,.07);
}
.ab-14__brand{
font-size:1.1rem;
font-weight:800;
color:#f1f5f9;
letter-spacing:-.01em;
}
.ab-14__btn{
display:flex;
flex-direction:column;
justify-content:center;
gap:6px;
width:40px;height:40px;
background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.1);
border-radius:8px;
cursor:pointer;
outline:none;
align-items:center;
transition:background .25s;
}
.ab-14__btn:hover{background:rgba(255,255,255,.1)}
.ab-14__btn.is-open{background:rgba(124,58,237,.2);border-color:rgba(124,58,237,.4)}
.ab-14__bar{
display:block;
width:22px;height:2px;
background:#e2e8f0;
border-radius:2px;
transform-origin:center;
transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .3s,background .3s;
}
.ab-14__btn.is-open .ab-14__bar:nth-child(1){transform:translateY(8px) rotate(45deg);background:#a78bfa}
.ab-14__btn.is-open .ab-14__bar:nth-child(2){opacity:0;transform:scaleX(0)}
.ab-14__btn.is-open .ab-14__bar:nth-child(3){transform:translateY(-8px) rotate(-45deg);background:#a78bfa}
.ab-14__menu{
display:flex;
flex-direction:column;
overflow:hidden;
max-height:0;
transition:max-height .4s cubic-bezier(.4,0,.2,1),opacity .3s;
opacity:0;
padding:0 1.5rem;
}
.ab-14__menu.is-open{max-height:200px;opacity:1;padding:1rem 1.5rem}
.ab-14__link{
display:block;
padding:.6rem 0;
font-size:.95rem;
font-weight:600;
color:#94a3b8;
text-decoration:none;
border-bottom:1px solid rgba(255,255,255,.05);
transition:color .2s;
}
.ab-14__link:last-child{border-bottom:none}
.ab-14__link:hover{color:#f1f5f9}
@media(prefers-reduced-motion:reduce){
.ab-14__bar,.ab-14__menu{transition:none}
} .ab-14,.ab-14 *,.ab-14 *::before,.ab-14 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-14 ::selection{background:#7c3aed;color:#fff}
.ab-14{
font-family:system-ui,sans-serif;
background:#0f172a;
min-height:100vh;
}
.ab-14__nav{
display:flex;
align-items:center;
justify-content:space-between;
padding:1rem 1.5rem;
border-bottom:1px solid rgba(255,255,255,.07);
}
.ab-14__brand{
font-size:1.1rem;
font-weight:800;
color:#f1f5f9;
letter-spacing:-.01em;
}
.ab-14__btn{
display:flex;
flex-direction:column;
justify-content:center;
gap:6px;
width:40px;height:40px;
background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.1);
border-radius:8px;
cursor:pointer;
outline:none;
align-items:center;
transition:background .25s;
}
.ab-14__btn:hover{background:rgba(255,255,255,.1)}
.ab-14__btn.is-open{background:rgba(124,58,237,.2);border-color:rgba(124,58,237,.4)}
.ab-14__bar{
display:block;
width:22px;height:2px;
background:#e2e8f0;
border-radius:2px;
transform-origin:center;
transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .3s,background .3s;
}
.ab-14__btn.is-open .ab-14__bar:nth-child(1){transform:translateY(8px) rotate(45deg);background:#a78bfa}
.ab-14__btn.is-open .ab-14__bar:nth-child(2){opacity:0;transform:scaleX(0)}
.ab-14__btn.is-open .ab-14__bar:nth-child(3){transform:translateY(-8px) rotate(-45deg);background:#a78bfa}
.ab-14__menu{
display:flex;
flex-direction:column;
overflow:hidden;
max-height:0;
transition:max-height .4s cubic-bezier(.4,0,.2,1),opacity .3s;
opacity:0;
padding:0 1.5rem;
}
.ab-14__menu.is-open{max-height:200px;opacity:1;padding:1rem 1.5rem}
.ab-14__link{
display:block;
padding:.6rem 0;
font-size:.95rem;
font-weight:600;
color:#94a3b8;
text-decoration:none;
border-bottom:1px solid rgba(255,255,255,.05);
transition:color .2s;
}
.ab-14__link:last-child{border-bottom:none}
.ab-14__link:hover{color:#f1f5f9}
@media(prefers-reduced-motion:reduce){
.ab-14__bar,.ab-14__menu{transition:none}
}(function(){
var btn=document.getElementById('ab-14-btn');
var menu=document.getElementById('ab-14-menu');
if(!btn||!menu)return;
btn.addEventListener('click',function(){
var open=btn.classList.toggle('is-open');
menu.classList.toggle('is-open',open);
btn.setAttribute('aria-expanded',open.toString());
menu.setAttribute('aria-hidden',(!open).toString());
});
})(); (function(){
var btn=document.getElementById('ab-14-btn');
var menu=document.getElementById('ab-14-menu');
if(!btn||!menu)return;
btn.addEventListener('click',function(){
var open=btn.classList.toggle('is-open');
menu.classList.toggle('is-open',open);
btn.setAttribute('aria-expanded',open.toString());
menu.setAttribute('aria-hidden',(!open).toString());
});
})();How this works
Three span elements inside the button represent the three bars. In the open state they are vertically stacked with equal height: 2px; width: 24px. When the .is-open class is added by JS, the top bar rotates 45deg and translates downward to the centre (translateY(8px) rotate(45deg)), the middle bar fades out via opacity: 0; transform: scaleX(0), and the bottom bar rotates -45deg and translates upward (translateY(-8px) rotate(-45deg)). The two outer bars cross at centre, forming the ×.
All transitions share the same 0.3s cubic-bezier(.4,0,.2,1) easing so the three motions are synchronized. A subtle transform-origin: center on each bar ensures rotation pivots around the bar's midpoint rather than its corner. The button also receives a background-color and border-radius transition so the tap target visually responds to the state change.
Customize
- Change the bar colour by updating the
backgroundon.ab-14__bar— the open state can use a different colour by targeting.ab-14__btn.is-open .ab-14__bar. - Widen the bars for a bolder icon by increasing
widthfrom24pxto28pxand adjusting thetranslateYoffset to match the new spacing. - Add a rotation to the entire button on open by adding
transform: rotate(90deg)to.ab-14__btn.is-openfor a spinning close effect. - Animate the nav menu simultaneously by targeting a sibling element in JS when toggling the class, so the menu slides down as the icon morphs.
- Use a different close symbol (e.g. an arrow) by changing the top bar to
rotate(-45deg) translateY(4px) translateX(-4px) scaleX(.6)instead of a full cross.
Watch out for
- The
translateYvalue for the cross formation must exactly match the spacing between the bars — if you change the bar gap, recalculate the offset (it equals the gap between bar centres). - Adding the class directly in the
clickhandler is synchronous and fires the transition immediately — do not wrap inrequestAnimationFrameor the initial state may not have been painted. - On touch devices, the
:focus-visiblering may appear after tap; addoutline: nonespecifically for touch by checking:not(:focus-visible)selectors for polish.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 36+ | 9+ | 16+ | 36+ |
CSS transforms and transitions are universally supported; the JS toggle pattern works in all modern browsers.