Morphing Hamburger
Three CSS lines pivot into an X with back-ease spring — the most recognized open/closed state indicator in mobile UI.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Navigation has 36 effects, including 4 free. Explore the category →
3 usage examples



How it works
The effect uses three <span class="line"> elements positioned absolutely inside a 36×36 px flexbox container. In hamburger state, lines 1 and 3 are offset vertically via translateY(±8px); line 2 is deliberately shorter (18 px vs 24 px) — a subtle asymmetry that gives the resting icon its character.
The transformation is entirely CSS-driven: when the .morphed class is added to the container, lines 1 and 3 return to Y=0 and rotate(±45deg) to form an X, while line 2 disappears via width: 0 and opacity: 0. No JS is involved in the rendering.
The cubic-bezier(.68, -.55, .27, 1.55) easing is a back ease-out: the lines slightly overshoot their target angle before settling back to the final position. This mechanical spring — invisible with a linear transition — reinforces the perception of weight and response without any JS. The 0.4 s duration lets the eye follow the motion without perceived lag.
JavaScript is limited to a single classList.toggle('morphed') triggered on click. If the user clicks before the transition ends, the browser resumes the animation from the current intermediate state and reverses it — native browser behavior, no manual state management required.
Accessibility
- prefers-reduced-motion absent: no
@media (prefers-reduced-motion: reduce)rule is present in the code. The transition always runs regardless of OS settings. Add manually if needed:.morphing-hamburger .line { transition: none; }inside the media query. - The trigger element is a
<div>with a JS handler, not a native<button>— not keyboard-focusable by default. Addtabindex="0"and akeydownhandler (Enter/Space) for WCAG 2.1 compliance. - No
aria-expandedpresent: screen readers cannot tell whether the menu is open or closed. Togglearia-expanded="false"/"true"on click in the handler. - No
aria-label: the control has no accessible name. Addaria-label="Open menu"updated dynamically on each toggle. - Line contrast (
#fffon#0a0a0f): ratio > 21:1 — WCAG AA and AAA compliant unconditionally.
Browser compatibility
Requires CSS Transitions and classList.toggle — available in every modern browser since 2014.
Without JavaScript, the icon stays static in hamburger state — no broken styling, no errors. Without CSS transition support (IE9 and older), the hamburger-to-X change happens instantly with no animation.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="morphing-hamburger" id="morphingHamburger">
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| background (on .morphing-hamburger .line) | #fff | Line color. Change to a brand color or use currentColor to inherit from the parent text color. |
| width (on .line) | 24px | Width of lines 1 and 3. Adjust line 2 (.line:nth-child(2), 18 px) in parallel to maintain the asymmetric ratio — e.g. 20 px / 15 px for a compact icon. |
| height (on .line) | 2px | Line thickness. 3 px for a bold look, 1 px for ultra-thin. |
| transition (on .line) | .4s cubic-bezier(.68,-.55,.27,1.55) | Animation duration and curve. Reduce to .2s for a snappier response, or swap the curve for ease-in-out to remove the overshoot and get a clean transition. |
| translateY (lines 1 and 3) | ±8px | Vertical gap between lines in hamburger state. Reduce to ±6 px for a compact icon, increase to ±10 px for a more open look. |
| width / height (on .morphing-hamburger) | 36px / 36px | Container and click-target size. Set to at least 44 px to meet WCAG touch target size recommendations (2.75 rem). |
| border-radius (on .line) | 2px | End-cap rounding. 0 for sharp square ends, 4 px for a softer rounded look. |
FAQ
<div class="morphing-hamburger" id="morphingHamburger"> with a <button> carrying the same class and id. Add background: none; border: none; padding: 0; cursor: pointer; in CSS to neutralise browser default styles. The JS toggle is unchanged; update aria-label and aria-expanded on click for WCAG compliance.classList.toggle('morphed') manages the visual state of the icon. In the same handler, trigger your menu: nav.classList.toggle('open'), a height transition, or a transform: translateX. The icon and menu share the same toggle and stay in sync at all times with no extra state.