Hamburger Button

An animated hamburger button that smoothly toggles between menu and close (X) states.

1. Copy the component file

Create a new file called hamburger-button.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

hamburger-button.jsx

import { memo } from "react";
import styles from "./hamburger-button.module.css";

const HamburgerButton = (props) => {
  const {
    size = 24,
    active = false,
    className,
    style,
    ...restProps
  } = props;

  const _size = Math.max(16, size);

  return (
    <button
      aria-label="Toggle menu"
      aria-expanded={active}
      {...restProps}
      className={[
        className,
        styles["hamburger-button"],
        active ? styles["active"] : "",
      ].join(" ")}
      style={{
        ...style,
        "--size": `${_size}px`,
      }}
    >
      <span aria-hidden={true} />
      <span aria-hidden={true} />
      <span aria-hidden={true} />
      <span aria-hidden={true} />
      <span aria-hidden={true} />
    </button>
  )
};

export default memo(HamburgerButton);

2. Copy the CSS module file

In the same folder, create a file called hamburger-button.module.css and paste the following CSS.

hamburger-button.module.css

.hamburger-button {
  --background-color: #fbfbfb;
  --border-color: #eeeeee;
  --stroke-color: #111111;
  --size: 24px;

  @media (prefers-color-scheme: dark) {
    --background-color: #1d1d1d;
    --border-color: #222222;
    --stroke-color: #f5f5f5;
  }

  position: relative;
  width: var(--size);
  height: var(--size);
  background: var(--background-color);
  border: 1px solid var(--border-color);
  border-radius: 2px;
  overflow: hidden;
  cursor: pointer;

  >span {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 65%;
    height: 2px;
    background: var(--stroke-color);

    &:nth-child(1) {
      top: 30%;
      transition: left 400ms ease 350ms;
    }

    &:nth-child(2) {
      transition: left 400ms ease 250ms;
    }

    &:nth-child(3) {
      transition: left 400ms ease 150ms;
      top: 70%;
    }

    &:nth-child(4), 
    &:nth-child(5) {
      transition:
        left 250ms ease-in-out 0ms,
        transform 250ms ease-in-out 0ms;
      left: 150%;
    }
  }

  &:disabled {
    cursor: initial;
  }

  &.active {
    span {
      &:nth-child(1) {
        left: -150%;
        transition: left 400ms ease 300ms;
      }

      &:nth-child(2) {
        left: -150%;
        transition: left 400ms ease 200ms;
      }

      &:nth-child(3) {
        left: -150%;
        transition: left 400ms ease 100ms;
      }

      &:nth-child(4),
      &:nth-child(5) {
        left: 50%;
        transition:
          transform 300ms ease 350ms,
          left 300ms ease 350ms;
      }

      &:nth-child(4) {
        transform: translate(-50%, -50%) rotate(45deg);
      }

      &:nth-child(5) {
        transform: translate(-50%, -50%) rotate(-45deg);
      }
    }
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

hamburger-button-preview.jsx

import HamburgerButton from "@/components/interactions/hamburger-button/hamburger-button";
import { useState } from "react";

const HamburgerButtonPreview = () => {
  const [active, setActive] = useState(false);

  return (
    <HamburgerButton
      size={48}
      active={active}
      onClick={() => setActive(!active)}
    />
  )
};

export default HamburgerButtonPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
sizenumberNo24Controls the width and height of the hamburger icon in pixels.
activebooleanNofalseWhen true, the hamburger transforms into a a close (X) icon.
onClick(event: React.MouseEvent) => voidNoCallback fired when the button is clicked.
disabledbooleanNofalseDisables the button interaction.
aria-labelstringNo"Toggle menu"Accessible label describing the button action for screen readers.
aria-controlsstringNoThe id of the menu element that this button controls.
classNamestringNoOptional CSS class names applied to the component for custom styling.
styleReact.CSSPropertiesNoInline styles applied to the root element.