GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 CSS Hamburger Menu Animations, No Library

A css hamburger menu animation is the motion a three-bar icon plays as a menu opens, built from transform and opacity rather than a JavaScript library.

Auto-plays · click a tile to jump to its section · all nine in one zip

A hamburger menu icon css animation is the same three bars in 01 through 04; only the geometry of how they meet changes — a plain X, a spin, an arrow, and a version that clears the middle bar first before the other two cross. 05 and 06 change what shows up behind the icon instead: a fullscreen panel that slides up, and a browser-native popover that needs no menu logic written in CSS at all. 07 through 09 layer motion onto whichever shape you picked — links that arrive one after another, a reveal shaped like the icon's own tap point, and a background that blurs to hand focus to the menu. Watch all nine loop in the grid above; every code block below keeps only the lines that make each one move, and the untrimmed files are in the zip. When a panel like 05 grows past covering the screen into replacing it, that's the territory of the CSS page transitions, and when a single row expands in place instead of a menu taking over, the CSS accordion animations are the closer fit.

01Bars morph to X

What you see

The top and bottom bars rotate 45 degrees into an X while the middle bar fades out beneath them, the whole move riding on transform and opacity over 300ms.

Bars morph to X
Open in a new tab

01 · Bars morph to X — Measured area 9.553% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

300ms45deg가운데 페이드

Where a css hamburger icon animation fits

It's the default hamburger button for a header — the shape most people expect before they've even read the icon.

.bar {
  position: absolute; left: 50%; top: 50%;
  width: 24px; height: 3px; border-radius: 2px;
  background: $color;
  transition: transform $duration $easing, opacity $duration $easing;
}
.bar1 { transform: translate(-50%, -50%) translateY(-7px); }
.bar2 { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
.bar3 { transform: translate(-50%, -50%) translateY(7px); }

.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }

02Spin into X

The motion

The whole icon spins a full 180 degrees while the bars fold into the same X as item 01, so the rotation reads as one continuous gesture rather than two separate moves stacked on top of each other.

Spin into X
Open in a new tab

02 · Spin into X — Measured area 13.016% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

180deg350ms역회전

Good places for it

It suits mobile navigation that wants opening the menu to feel more deliberate than a plain fade.

.burger {
  transform: rotate(0deg);
  transition: transform $duration $easing;
}
.stage.is-open .burger { transform: rotate(180deg); }

.bar1 { transform: translate(-50%, -50%) translateY(-7px); }
.bar2 { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
.bar3 { transform: translate(-50%, -50%) translateY(7px); }

.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }

03Morph to arrow

What it does

The three bars narrow and tilt until they read as a left-pointing arrow instead of an X, using the same rotate approach as item 01 but with a shorter travel distance and each bar's transform-origin pinned to its far end.

Morph to arrow
Open in a new tab

03 · Morph to arrow — Measured area 13.206% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

250ms오른쪽 화살표각도 30deg

When to use it

It fits a menu button that doubles as a back action, since the icon itself hints at what tapping it will do.

.bar {
  position: absolute; right: 50%; top: 50%;
  transform-origin: right center;
  transition: transform $duration $easing, opacity $duration $easing;
}
.bar1 { transform: translate(50%, -50%) translateY(-7px); }
.bar2 { transform: translate(50%, -50%) scaleX(1); }
.bar3 { transform: translate(50%, -50%) translateY(7px); }

.stage.is-open .bar1 { transform: translate(50%, -50%) translateY(-3px) rotate(-35deg); }
.stage.is-open .bar2 { transform: translate(50%, -50%) scaleX(.7); }
.stage.is-open .bar3 { transform: translate(50%, -50%) translateY(3px) rotate(35deg); }

04Middle bar slides out

What you see

The middle bar slides sideways and fades out before the top and bottom bars rotate into their X, so the three bars never cross paths mid-transition the way they do in item 01. It works best in wide headers with room to spare, where that extra half-step has space to actually read.

Middle bar slides out
Open in a new tab

04 · Middle bar slides out — Measured area 13.229% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

오른쪽으로280ms왼쪽으로

In the code

.bar1 { transform: translate(-50%, -50%) translateY(-7px); }
.bar2 {
  transform: translate(-50%, -50%) translateX(0);
  opacity: 1;
  transition: transform $duration $easing, opacity $duration $easing;
}
.bar3 { transform: translate(-50%, -50%) translateY(7px); }

.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) translateX(140%); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }

05Fullscreen overlay menu

The motion

A full-screen color panel rises from the bottom over 600ms via transform: translateY(), covering the page as the bars behind it collapse into an X.

Fullscreen overlay menu
Open in a new tab

05 · Fullscreen overlay menu — Measured area 99.997% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

400ms위로좌우 슬라이드

Good places for it

It's built for mobile navigation carrying more links than a small dropdown can hold without feeling cramped.

.panel {
  position: absolute; inset: 0;
  transform: translateY(100%);
  transition: transform $duration $easing;
}
.stage.is-open .panel { transform: translateY(0); }

.bar1 { transform: translate(-50%, -50%) translateY(-7px); }
.bar2 { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
.bar3 { transform: translate(-50%, -50%) translateY(7px); }

.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }

06Popover API menu, zero JS

What it does

The menu opens with nothing but the popover attribute on the panel and popovertarget on the button, so the open state, the light dismiss on an outside click, and the top-layer stacking all come from the browser instead of a script.

Popover API menu, zero JS
Open in a new tab

06 · Popover API menu, zero JS — Measured area 12.977% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

popoveranchor자동 닫힘

When to use it

That makes it a good fit for static sites that need a working menu without shipping a JavaScript build step at all.

<button class="burger" popovertarget="menu" type="button" aria-label="Open menu">
  <span class="bar bar1"></span>
  <span class="bar bar2"></span>
  <span class="bar bar3"></span>
</button>
<div id="menu" class="menu" popover aria-label="Menu">
  <a class="menu__link" href="#">Home</a>
  <a class="menu__link" href="#">About</a>
  <a class="menu__link" href="#">Contact</a>
</div>

07Staggered link reveal

What you see

Once the panel is open, each link fades in roughly 30ms after the one above it, using a single custom property multiplied into transition-delay instead of a loop of setTimeout calls.

Staggered link reveal
Open in a new tab

07 · Staggered link reveal — Measured area 12.543% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

30ms역순위에서

Where it fits

It reads well on dropdown menus with four to six links, where an instant reveal feels flat and a full stagger animation feels like overkill.

.menu__link {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity $duration $ease-spring, transform $duration $ease-spring;
  transition-delay: calc(var(--i) * #{$stagger});
}

.stage.is-open .menu { opacity: 1; pointer-events: auto; }
.stage.is-open .menu__link { opacity: 1; transform: translateY(0); }

08Circle reveal from the icon

The motion

A circle grows outward from the icon's own position until it covers the screen, driven by animating a single clip-path: circle() value rather than swapping in a whole new stacking layer.

Circle reveal from the icon
Open in a new tab

08 · Circle reveal from the icon — Measured area 99.997% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

clip-path circle450ms오른쪽 위에서

Good places for it

It suits an icon button next to a logo, or a landing page that wants the reveal itself to carry some visual weight.

.reveal {
  position: absolute; inset: 0;
  clip-path: circle(0% at calc(100% - 30px) 30px);
  transition: clip-path $duration $easing;
}
.stage.is-open .reveal {
  clip-path: circle(150% at calc(100% - 30px) 30px);
}

.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }

09Backdrop blur and dim

What it does

The page behind the menu blurs by 6px and dims by about 40 percent as the panel opens, both driven by a single filter transition on the content itself rather than a separate overlay element.

Backdrop blur and dim
Open in a new tab

09 · Backdrop blur and dim — Measured area 20.247% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

6px어둡게 40%10px

When to use it

It's built for content-heavy pages, where an overlay menu needs to visually pull away from everything still readable underneath it.

.page {
  transition: filter $dur-base $ease-spring;
}
.stage.is-open .page { filter: blur($blur) brightness(1 - $dim); }

.menu {
  opacity: 0;
  transform: translateY(-8px) scale(.96);
  transition: opacity $dur-base $ease-spring, transform $dur-base $ease-spring;
}
.stage.is-open .menu { opacity: 1; transform: translateY(0) scale(1); }

Where it breaks — the traps

clip-path and backdrop-filter support

08's reveal is a single animating clip-path, and support for clip-path itself is safe in every modern browser today, as MDN's clip-path reference confirms. 09 could have reached for backdrop-filter on a floating overlay instead of filtering .page directly, but backdrop-filter only ships unprefixed in Safari from version 18, alongside Chrome 76+ and Firefox 103+, per MDN's backdrop-filter browser compatibility table — filtering the actual content node sidesteps that gap, which is the real reason the demo does it that way rather than layering a blurred sheet on top. The delay math behind 07's one-after-another links is worth borrowing outside a menu too — it runs vertically, step by step, through the CSS timeline animations.

Trap two — popover needs styling

The second trap is item 06's popover: popovertarget and the popover attribute hand you the open state, the outside-click dismiss, and the top-layer stacking for free, but they don't hand you a transition unless you also style the :popover-open selector — skip that, and the panel still works — it just snaps open with no motion at all, which looks like a bug rather than a missing style. The zip's password is 2cpj9dyd, sitting in this paragraph the same plain way it sits folded into the SCSS file itself, easy to miss if you're skimming for a code block instead of reading the sentence.

Trap three — the preview loop

The third trap is the preview above: the 3×3 grid sits inside a fixed, non-scrollable iframe, so every icon there opens and closes on its own two-second loop instead of waiting for a tap — watch it rather than trying to click any of the nine squares.

Accessibility

Reduced motion

prefers-reduced-motion strips something different from each family here rather than one blanket "turn everything off." 01 through 05 and 08 keep every open and closed position exactly where it was; they just collapse the transition duration down to near-nothing, since a menu that can't move from closed to open at all would be broken rather than calmer. 09's blur and dim behave the same way — the page still darkens and softens, only instantly instead of over a fade. 06's popover motion collapses to the same instant swap, since the actual open and close mechanism is the popover attribute itself, not the transition riding on top of it. 07 is the one exception worth calling out on its own: the stagger's transition-delay resets to zero, so every link appears together instead of one after another, because a 30ms cascade reads as decoration once someone has asked their system to skip it. MDN's prefers-reduced-motion guide has the full browser support table.

@media (prefers-reduced-motion: reduce) {
  .bar, .menu, .panel, .reveal, .page { transition-duration: 1ms; }
  .menu__link { transition-duration: 1ms; transition-delay: 0ms; }
}

A css hamburger menu animation codepen is easy to find, but more of these transition patterns live in the CSS category, and what this project actually is gets explained on the about page.

FAQ

Does a css hamburger menu animation need JavaScript?

Only to toggle a class in most of these — items 01 through 05 and 07 through 09 swap a class like is-open on click, and that single click handler is the only script involved, since the motion itself is transform, opacity, filter, and clip-path written in CSS. Item 06 needs no script at all, because popovertarget and popover handle the open state, the outside-click dismiss, and the Escape key on their own.

What's the difference between a plain overlay panel and a clip-path reveal?

An overlay like item 05's panel is a whole extra element that slides or fades into place on top of everything else, while item 08's clip-path animates the visible shape of one element that's already sitting there, growing a circle instead of moving a box across the screen. Clip-path tends to be cheaper to animate since the browser only recalculates the clipping region rather than layout, but the reveal shape is limited to whatever clip-path can describe — a circle here, anchored to the icon's own corner.

Can an html css burger menu animation run inside a React or Vue component?

Yes. Every effect here is scoped to class names and CSS custom properties, so a component only needs to add or remove is-open on click; nothing in the SCSS reaches into the DOM beyond what a framework already renders for you. The zip's react/ folder has a working version of each item as a small component, in case you'd rather start from that than wire up the class toggle by hand. On wider screens the same navigation usually wants to unfold rather than take over, and that shape is collected separately in the CSS dropdown menu animations.

Enter the archive password

The password is inside this article. You will find it as you read.