Hamburger

A toggle button with smooth, spring-like SVG arc transitions, stroke dash animations, and optional callbacks for state change inspired by Daniyal Asif

Loading demo...

default.tsx
"use client";
 
import { useState, startTransition } from "react";
import { Button } from "@/components/ui/button";
 
export interface HamburgerMenuProps {
  strokeWidth?: number;
 
  defaultChecked?: boolean;
  onToggle?: (checked: boolean) => void;
}
 
const HamburgerMenu = (props: HamburgerMenuProps) => {
  const { strokeWidth = 3, defaultChecked = false } = props;
 
  const [isChecked, setIsChecked] = useState(defaultChecked);
 
  const handleChange = () => {
    const next = !isChecked;
    startTransition(() => setIsChecked(next));
  };
 
  return (
    <div className="flex items-center justify-center bg-background size-full">
      <Button
        className="size-12"
        size="icon"
        onClick={handleChange}
        variant="ghost"
      >
        <svg
          viewBox="0 0 32 32"
          style={{
            transform: isChecked ? "rotate(-45deg)" : "rotate(0deg)",
            transition: "transform 600ms cubic-bezier(0.4, 0, 0.2, 1)",
          }}
          className="size-10 stroke-primary"
        >
          <path
            d="M27 10 13 10C10.8 10 9 8.2 9 6 9 3.5 10.8 2 13 2 15.2 2 17 3.8 17 6L17 26C17 28.2 18.8 30 21 30 23.2 30 25 28.2 25 26 25 23.8 23.2 22 21 22L7 22"
            fill="none"
            strokeWidth={strokeWidth}
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeDasharray={isChecked ? "20 300" : "12 63"}
            strokeDashoffset={isChecked ? -32.42 : 0}
            style={{
              transition:
                "stroke-dasharray 600ms cubic-bezier(0.4, 0, 0.2, 1), stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1)",
            }}
          />
 
          <path
            d="M7 16 27 16"
            fill="none"
            strokeWidth={strokeWidth}
            strokeLinecap="round"
            strokeLinejoin="round"
            style={{
              transition: "opacity 300ms ease",
            }}
          />
        </svg>
      </Button>
    </div>
  );
};
 
export default HamburgerMenu;