Hamburger
An animated menu icon that smoothly morphs into a close icon
Last updated on
Manual
Install the following dependencies:
npm install react-native-reanimated expo-blurCopy 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
