Hamburger

An animated menu icon that smoothly morphs into a close icon

Last updated on

Edit on GitHub

Manual

Install the following dependencies:

npm install react-native-reanimated expo-blur

Copy and paste the following code into your project. component/micro-interactions/hamburger.tsx

import React, { memo } from "react";import {  Platform,  Pressable,  StyleSheet,  type StyleProp,  type ViewStyle,} from "react-native";import Animated, {  interpolate,  useAnimatedProps,  useAnimatedStyle,} from "react-native-reanimated";import type { HamburgerIconProps } from "./types";import { BlurView, BlurViewProps } from "expo-blur";const AnimatedBlurView =  Animated.createAnimatedComponent<BlurViewProps>(BlurView);export const HamburgerIcon: React.FC<HamburgerIconProps> =  memo<HamburgerIconProps>(    ({      progress,      size = 54,      color = "black",      strokeWidth,      lineGap,      lineWidth,      onPress,      style,    }: HamburgerIconProps): React.JSX.Element & React.ReactNode => {      const calculatedStrokeWidth: number = strokeWidth ?? size * 0.04;      const calculatedLineWidth: number = lineWidth ?? size * 0.75;      const calculatedLineGap: number = lineGap ?? size * 0.25;      const lineHeight: number = calculatedStrokeWidth;      const animatedBlurViewPropz = useAnimatedProps<BlurViewProps>(() => {        const intensity = interpolate(          progress.value,          [0, 0.3, 0.6, 1],          [0, 2.5, 9.5, 0],        );        return {          intensity,        };      });      const topStyle = useAnimatedStyle<ViewStyle>(() => ({        transform: [          {            translateY: interpolate(              progress.value,              [0, 1],              [-calculatedLineGap, 0],            ),          },          { rotate: `${interpolate(progress.value, [0, 1], [0, 45])}deg` },        ],      }));      const middleStyle = useAnimatedStyle<ViewStyle>(() => ({        opacity: interpolate(progress.value, [0, 0.9, 1], [1, 0, 0]),      }));      const bottomStyle = useAnimatedStyle<ViewStyle>(() => ({        transform: [          {            translateY: interpolate(              progress.value,              [0, 1],              [calculatedLineGap, 0],            ),          },          { rotate: `${interpolate(progress.value, [0, 1], [0, -45])}deg` },        ],      }));      const lineBaseStyle: StyleProp<ViewStyle> = {        width: calculatedLineWidth,        height: lineHeight,        backgroundColor: color,        borderRadius: calculatedStrokeWidth,        position: "absolute" as const,        shadowColor: color,        shadowOffset: { width: 0, height: 0 },        shadowOpacity: 0.8,        shadowRadius: 4,      };      return (        <Pressable          onPress={onPress}          style={[            {              width: size,              height: size,              alignItems: "center",              justifyContent: "center",            },            style,          ]}        >          <Animated.View style={[lineBaseStyle, topStyle]} />          <Animated.View style={[lineBaseStyle, middleStyle]} />          <Animated.View style={[lineBaseStyle, bottomStyle]} />          {Platform.OS === "ios" && (            <AnimatedBlurView              animatedProps={animatedBlurViewPropz}              tint={"prominent"}              style={[                StyleSheet.absoluteFillObject,                {                  overflow: "hidden",                },              ]}              pointerEvents={"none"}            />          )}        </Pressable>      );    },  );export default memo<HamburgerIconProps>(HamburgerIcon);

Usage

import { View, Text, StyleSheet } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { useFonts } from "expo-font";import { SymbolView } from "expo-symbols";import { CountdownTimer } from "@/components/micro-interactions/countdown";import { Ionicons } from "@expo/vector-icons";import { FlexiButton } from "@/components/micro-interactions/flexi-button";import GooeySwitch from "@/components/micro-interactions/gooey-switch";import Hamburger from "@/components/micro-interactions/hamburger";import { useSharedValue, withTiming } from "react-native-reanimated";export default function App() {  const [fontLoaded] = useFonts({    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),    Coolvetica: require("@/assets/fonts/Coolvetica-Rg.otf"),  });  const launchDate = new Date("2026-07-20T14:30:00");  const progress = useSharedValue(0);  return (    <GestureHandlerRootView style={styles.container}>      <StatusBar style="light" />      <View style={styles.content}>        <Hamburger          progress={progress}          color="#fff"          size={90}          onPress={() => {            progress.value = withTiming(progress.value === 1 ? 0 : 1);          }}        />      </View>    </GestureHandlerRootView>  );}const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: "#0a0a0a",  },  content: {    alignItems: "center",    gap: 24,    top: 80,  },  iconBox: {    width: 64,    height: 64,    borderRadius: 20,    backgroundColor: "#1a1a1a",    justifyContent: "center",    alignItems: "center",    marginBottom: 8,  },  label: {    fontSize: 14,    color: "#555",    textTransform: "uppercase",    letterSpacing: 2,  },  date: {    fontSize: 15,    color: "#333",    marginTop: 8,  },});

Props

React Native Reanimated
Expo Blur