AniUI

Sidebar

Collapsible side navigation panel with a toggle trigger, animated with Reanimated.

Dashboard
Analytics
Projects
Team
Settings

Content for the Dashboard section.

Web preview — components render natively on iOS & Android
import { View } from "react-native";
import { SidebarProvider, Sidebar, SidebarTrigger } from "@/components/ui/sidebar";
import { Text } from "@/components/ui/text";

export function MyScreen() {
return (
  <SidebarProvider defaultOpen>
    <Sidebar width={256}>
      <Text>Dashboard</Text>
      <Text>Projects</Text>
      <Text>Settings</Text>
    </Sidebar>
    <View className="flex-1 p-3">
      <SidebarTrigger />
      <Text>Content area</Text>
    </View>
  </SidebarProvider>
);
}

Installation

npx @aniui/cli add sidebar

Usage

import { View } from "react-native";
import { SidebarProvider, Sidebar, SidebarTrigger } from "@/components/ui/sidebar";
import { Text } from "@/components/ui/text";

export function MyScreen() {
  return (
    <SidebarProvider defaultOpen>
      <Sidebar width={256}>
        <Text>Dashboard</Text>
        <Text>Settings</Text>
      </Sidebar>
      <View className="flex-1 p-3">
        <SidebarTrigger />
        <Text>Content area</Text>
      </View>
    </SidebarProvider>
  );
}

The useSidebar() hook exposes the sidebar state (open, setOpen, toggle) to any descendant of SidebarProvider.

Props

SidebarProvider

PropTypeDefault
defaultOpen
boolean
true
children
React.ReactNode
—

Sidebar

PropTypeDefault
width
number
256
children
React.ReactNode
—
className
string
—

Also accepts all View props from React Native.

SidebarTrigger

PropTypeDefault
children
React.ReactNode
—
className
string
—

Also accepts all Pressable props from React Native.

useSidebar()

Returns { open: boolean; setOpen: (open: boolean) => void; toggle: () => void }. Must be called within a SidebarProvider.

Accessibility

  • Sidebar uses accessibilityRole="menu".
  • SidebarTrigger uses accessibilityRole="button" with a 48dp minimum touch target.

Source

components/ui/sidebar.tsx
import React, { createContext, useContext, useState } from "react";
import { View, Pressable } from "react-native";
import Animated, { useAnimatedStyle, withTiming } from "react-native-reanimated";
import { Menu } from "lucide-react-native";
import { duration } from "@/components/ui/animate";
import { cn } from "@/lib/utils";
import { useThemeColors } from "@/components/ui/theme-provider";

interface SidebarCtx {
  open: boolean;
  toggle: () => void;
  setOpen: (open: boolean) => void;
}
const Ctx = createContext<SidebarCtx | null>(null);

export function useSidebar(): SidebarCtx {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error("useSidebar must be used within <SidebarProvider>");
  return ctx;
}

export interface SidebarProviderProps {
  defaultOpen?: boolean;
  children: React.ReactNode;
}

export function SidebarProvider({ defaultOpen = true, children }: SidebarProviderProps) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <Ctx.Provider value={{ open, setOpen, toggle: () => setOpen((o) => !o) }}>
      <View className="flex-1 flex-row">{children}</View>
    </Ctx.Provider>
  );
}

export interface SidebarProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  width?: number;
  children?: React.ReactNode;
}

export function Sidebar({ className, width = 256, children, ...props }: SidebarProps) {
  const { open } = useSidebar();
  const style = useAnimatedStyle(() => ({
    width: withTiming(open ? width : 0, { duration: duration.normal }),
  }));
  return (
    <Animated.View style={[{ overflow: "hidden" }, style]}>
      <View
        className={cn("h-full border-r border-border bg-card p-3", className)}
        style={{ width }}
        accessibilityRole="menu"
        {...props}
      >
        {children}
      </View>
    </Animated.View>
  );
}

export interface SidebarTriggerProps extends React.ComponentPropsWithoutRef<typeof Pressable> {
  className?: string;
  children?: React.ReactNode;
}

export function SidebarTrigger({ className, children, ...props }: SidebarTriggerProps) {
  const { toggle } = useSidebar();
  const colors = useThemeColors();
  return (
    <Pressable
      onPress={toggle}
      accessibilityRole="button"
      accessible
      className={cn("min-h-12 min-w-12 items-center justify-center", className)}
      {...props}
    >
      {children ?? <Menu size={18} color={colors.foreground} />}
    </Pressable>
  );
}