20 CSS Animated Buttons14 / 20

CSS + JSMIT licensed

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.

Published

Live Demo
Try it

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>
.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());
  });
})();
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 Hamburger To Close Button
Source: https://codefronts.com/motion/css-animated-buttons/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
  }
}
```

## JavaScript
```js
(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.

Make it yours

  • Change the bar colour by updating the background on .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 width from 24px to 28px and adjusting the translateY offset to match the new spacing.
  • Add a rotation to the entire button on open by adding transform: rotate(90deg) to .ab-14__btn.is-open for 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.

Gotchas — read before shipping

  • The translateY value 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 click handler is synchronous and fires the transition immediately — do not wrap in requestAnimationFrame or the initial state may not have been painted.
  • On touch devices, the :focus-visible ring may appear after tap; add outline: none specifically for touch by checking :not(:focus-visible) selectors for polish.

Browser support

ChromeSafariFirefoxEdge
36+9+16+36+

CSS transforms and transitions are universally supported; the JS toggle pattern works in all modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…