AniUI

Drawer

A sliding panel that emerges from the left or right edge of the screen, ideal for navigation menus and side content.

Requires react-native-reanimated (Tier 2).

import { Drawer, DrawerContent } from "@/components/ui/drawer";

export function MyScreen() {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <Button onPress={() => setOpen(true)}>Open Drawer</Button>
      <Drawer open={open} onOpenChange={setOpen}>
        <DrawerContent>
          <Text>Drawer content here</Text>
        </DrawerContent>
      </Drawer>
    </>
  );
}

Installation

npx @aniui/cli add drawer

Usage

app/index.tsx
import { Drawer, DrawerContent } from "@/components/ui/drawer";

export function MyScreen() {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <Button onPress={() => setOpen(true)}>Open Drawer</Button>
      <Drawer open={open} onOpenChange={setOpen}>
        <DrawerContent>
          <Text>Drawer content here</Text>
        </DrawerContent>
      </Drawer>
    </>
  );
}

Left Drawer (Default)

<Drawer open={open} onOpenChange={setOpen} side="left">
  <DrawerContent>
    <Text>Left drawer content</Text>
  </DrawerContent>
</Drawer>

Right Drawer

<Drawer open={open} onOpenChange={setOpen} side="right">
  <DrawerContent>
    <Text>Right drawer content</Text>
  </DrawerContent>
</Drawer>

Props

Drawer

PropTypeDefault
open
boolean
required
onOpenChange
(open: boolean) => void
required
side
"left" | "right"
"left"
children
ReactNode
required

DrawerContent

PropTypeDefault
className
string
children
ReactNode

DrawerContent also accepts all View props from React Native.

Source

components/ui/drawer.tsx
import React, { useEffect } from "react";
import { View, Pressable, Modal } from "react-native";
import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS } from "react-native-reanimated";
import { cn } from "@/lib/utils";

export interface DrawerProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  side?: "left" | "right";
  children: React.ReactNode;
}
export function Drawer({ open, onOpenChange, side = "left", children }: DrawerProps) {
  const translate = useSharedValue(side === "left" ? -300 : 300);
  const opacity = useSharedValue(0);
  useEffect(() => {
    translate.value = withTiming(open ? 0 : (side === "left" ? -300 : 300), { duration: 250 });
    opacity.value = withTiming(open ? 0.5 : 0, { duration: 250 });
  }, [open, side, translate, opacity]);
  const overlayStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
  const drawerStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translate.value }],
  }));
  const close = () => onOpenChange(false);
  return (
    <Modal visible={open} transparent animationType="none" onRequestClose={close}>
      <Pressable className="absolute inset-0" onPress={close} accessible={false}>
        <Animated.View className="flex-1 bg-black" style={overlayStyle} />
      </Pressable>
      <Animated.View
        className={cn(
          "absolute top-0 bottom-0 w-72 bg-background border-border",
          side === "left" ? "left-0 border-r" : "right-0 border-l"
        )}
        style={drawerStyle}
        accessibilityRole="menu"
      >
        {children}
      </Animated.View>
    </Modal>
  );
}
export interface DrawerContentProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  children?: React.ReactNode;
}
export function DrawerContent({ className, ...props }: DrawerContentProps) {
  return <View className={cn("flex-1 p-4", className)} {...props} />;
}