DEV Community

Cover image for Stunning Animated Drawer Navigation in Expo React Native
haider mukhtar
haider mukhtar

Posted on

Stunning Animated Drawer Navigation in Expo React Native

💡 Why I Built This

While React Navigation’s standard drawer works, I wanted to create a visually immersive experience where screens dynamically respond to drawer interactions. The goal? An iOS-style navigation with:

  • Smooth 3D rotations
  • Scale/perspective animations
  • Custom profile integration
  • Seamless gesture control

🧩 Key Tech Stack

  • expo-router (File-based routing)
  • react-native-reanimated (Animations)
  • @react-navigation/drawer (Core navigation)
  • react-native-gesture-handler (Swipe gestures)

🚀 Step-by-Step Implementatio

1. Core Setup

npx create-expo-app@latest
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

Enter fullscreen mode Exit fullscreen mode

2. Magic Animation Wrapper (drawer-scene-wrapper.tsx)

This component makes screens dance when the drawer opens:

const DrawerSceneWrapper = ({ children }: { children: ReactNode }) => {
  const progress = useDrawerProgress();

  const animatedStyled = useAnimatedStyle(() => ({
    transform: [
      { scale: interpolate(progress.value, [0, 1], [1, 0.8]) },
      { translateX: interpolate(progress.value, [0, 1], [0, 170]) },
      { rotateY: `${interpolate(progress.value, [0, 1], [0, -25])}deg` }
    ],
    borderRadius: 20
  }));

  return <Animated.View style={[styles.container, animatedStyled]}>
    {children}
  </Animated.View>;
};

Enter fullscreen mode Exit fullscreen mode

3. Custom Drawer UI (custom-drawer-content.tsx)

Added user profile + logout button:

<SafeAreaView style={{ flex: 1, backgroundColor: "transparent" }}>
  {/* Profile Section */}
  <TouchableOpacity style={styles.userContainer}>
    <Image source={{ uri: "USER_AVATAR_URL" }} style={styles.userImage} />
    <View>
      <Text style={styles.userName}>Haider Mukhtar</Text>
      <Text style={styles.userEmail}>haider@example.com</Text>
    </View>
  </TouchableOpacity>

  {/* Navigation Items */}
  <DrawerContentScrollView {...props}>
    <DrawerItemList {...props} />
  </DrawerContentScrollView>

  {/* Logout Button */}
  <TouchableOpacity style={styles.logoutButton}>
    <Ionicons name="log-out" size={24} color="#FFFFFF" />
    <Text style={styles.logoutText}>Logout</Text>
  </TouchableOpacity>
</SafeAreaView>

Enter fullscreen mode Exit fullscreen mode

4. Drawer Configuration (_layout.tsx)

Styled the drawer with effects:

<GestureHandlerRootView style={{ flex: 1 }}>
  <Drawer
    drawerContent={(props) => <CustomDrawerContent {...props} />}
    screenOptions={{
      headerShown: false,
      drawerActiveBackgroundColor: "#33b3a6",
      drawerInactiveBackgroundColor: "transparent",
      drawerActiveTintColor: "#FFFFFF",
      drawerInactiveTintColor: "#FFFFFF",
      overlayColor: "transparent",
      drawerStyle: {
        backgroundColor: "transparent",
        width: "60%",
        paddingTop: 40,
      },
      drawerLabelStyle: {
        marginLeft: -6,
        fontSize: 18,
        fontFamily: "PoppinsMedium500",
        color: "#FFFFFF",
      },
      drawerItemStyle: {
        marginLeft: -16,
        marginRight: 35,
        borderTopLeftRadius: 0,
        borderBottomLeftRadius: 0,
        borderTopRightRadius: 50,
        borderBottomRightRadius: 50,
      },
      sceneStyle: {
        backgroundColor: "#26867C",
      },
      // drawerType: 'front',
    }}
  >
    <Drawer.Screen 
      name="index" 
      options={{ 
        title: "Home",
        drawerIcon: ({ color }) => <Ionicons name="home" color={color} />
      }} 
    />
    {/* Add other screens here */}
  </Drawer>
</GestureHandlerRootView>

Enter fullscreen mode Exit fullscreen mode

5. Screen Implementation (index.tsx)

Wrapped every screen in DrawerSceneWrapper:

const HomeScreen = () => {
  const navigation = useNavigation();
  return (
    <DrawerSceneWrapper>
      <View style={styles.header}>
        <TouchableOpacity 
          onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
        >
          <Ionicons name="menu" size={28} color="#000" />
        </TouchableOpacity>
        <Text style={styles.headerTitle}>Home</Text>
      </View>
      {/* Screen content */}
    </DrawerSceneWrapper>
  );
}

Enter fullscreen mode Exit fullscreen mode

👀 Demo Time!

Here’s what it looks like in action:

Animated Drawer Navigation Demo

🎨 Design Highlights

  • Animated Perspective 3D rotation + scaling synchronized with drawer gestures.
  • Bubble Menu Items Rounded active-state highlights with borderTopRightRadius: 50.

🚫 Common Pitfalls Solved

  • Gesture Conflicts: Wrapped root in <GestureHandlerRootView>.
  • Animation Jank: Used Extrapolation.CLAMP for smooth interpolation.
  • Safe Areas: Leveraged react-native-safe-area-context for notch support.
  • TypeScript: Strict typing for drawer props (DrawerContentComponentProps).

📱 Try It Yourself!

Clone the full project:

👉 Github Repo: Animated-Drawer-Navigation-Expo

💬 Final Thoughts

This implementation proves that with:

  • Reanimated’s interpolation magic ✨
  • Expo’s zero-config tooling 🧰
  • Strategic styling 🎨

… you can create navigation that delights users while maintaining code simplicity. What would you add to this? Share your ideas below! 👇

🔗 Haider Mukhtar on LinkedIn

Top comments (1)

Collapse
 
haider_mukhtar profile image
haider mukhtar