Skip to content

Drawer navigator

Drawer renders a swipeable side panel that switches between a fixed set of screens declared as screen children, the same fixed-route-list shape as Tab. Unlike Stack, which drives native react-native-screens push/pop transitions, Drawer paints its panel with ordinary View/Text primitives and drives the swipe gesture itself, built on PanResponder + Animated — not react-native-gesture-handler/react-native-reanimated. Only the focused route’s screen is ever mounted at a time, exactly like Tab.

Drawer ships no built-in menu UI — you supply the panel’s contents yourself, reading the same { state, descriptors, navigation } data in every adapter. How you supply it follows each framework’s own idiom: a render prop in React, a scoped slot in Vue, a named TemplateRef in Angular, a snippet in Svelte.

import { Drawer } from '@symbiote-native/navigation/react';
import type { IDrawerDescriptorMap, IDrawerNavigatorHandle, IDrawerRouterState } from '@symbiote-native/navigation';
function renderDrawerContent({ state, descriptors, navigation }: {
state: IDrawerRouterState;
descriptors: IDrawerDescriptorMap;
navigation: IDrawerNavigatorHandle;
}) {
return (
<SafeAreaView className="drawer-panel">
{state.routes.map(route => (
<Pressable key={route.key} onPress={() => navigation.jumpTo(route.name)}>
<Text>{descriptors[route.key]?.options.drawerLabel ?? route.name}</Text>
</Pressable>
))}
</SafeAreaView>
);
}
export function DrawerDemoScreen() {
return (
<Drawer
initialRouteName="Home"
drawerPosition="right"
drawerType="slide"
renderDrawerContent={renderDrawerContent}
>
<Drawer.Screen name="Home" component={DrawerHomeScreen} options={{ title: 'Home', drawerLabel: 'Home' }} />
<Drawer.Screen name="Settings" component={DrawerSettingsScreen} options={{ title: 'Settings', drawerLabel: 'Settings' }} />
</Drawer>
);
}

Any component under the Drawer — the screen’s own top-level component, or a button/menu action nested deeper in the tree — reads the drawer handle with useDrawerNavigation() in React/Vue/Svelte or injectDrawerNavigation() in Angular: the narrowed twin of useNavigation()/injectNavigation(), returning a concretely-typed IDrawerNavigatorHandle with openDrawer()/closeDrawer()/ toggleDrawer() directly, no union to narrow. See Hooks for the full hook API.

IDrawerNavigatorHandle — the shape of the object every adapter exposes (a forwarded ref in React, expose() in Vue, the Drawer class itself in Angular, the exported functions off bind:this in Svelte), identical across all four:

Method Signature Description
openDrawer () => void Opens the drawer, animating the panel/content/overlay to their open positions
closeDrawer () => void Closes the drawer, animating back to closed
toggleDrawer () => void Opens if closed, closes if open
jumpTo (name: string) => void Focuses the route named name and closes the drawer — selecting a destination IS the dismissal gesture. Unlike Tab’s jumpTo, takes no params argument

IDrawerOptions — passed as props/inputs on the navigator itself, there is no per-screen equivalent:

Option Type Default Description
drawerType 'front' | 'back' | 'slide' | 'permanent' 'front' Panel/content movement style — front: panel slides over static content; back: content slides to reveal a stationary panel; slide: both move together; permanent: static sidebar, no gesture/animation
drawerPosition 'left' | 'right' 'left' Which screen edge the drawer opens from
drawerWidth number 280 Panel width in px
overlayColor string 'rgba(0, 0, 0, 0.5)' Dimming overlay color (front/slide only)
swipeEnabled boolean true Enables edge-swipe-to-open and anywhere-swipe-to-close
swipeEdgeWidth number 32 How close to the position edge a closed-drawer swipe must start
swipeMinDistance number 60 Minimum drag distance to snap open/closed on release
swipeMinVelocity number (px/ms) 0.5 (= 500px/s) Minimum release velocity that snaps regardless of distance

IDrawerScreenOptions — passed as options on a single screen. Deliberately tiny: Drawer ships no built-in menu UI, so these fields exist purely for your own custom drawer-content UI to read off the descriptor map below.

Option Type Description
title string Fallback label for your own custom drawer content to fall back to
drawerLabel string Explicit label for your own custom drawer content to display

Every adapter hands your custom content the same data shape:

type IDrawerContentProps = {
state: IDrawerRouterState;
descriptors: IDrawerDescriptorMap;
navigation: IDrawerNavigatorHandle;
};

state.routes is the ordered route list, and descriptors is keyed by route.key:

type IDrawerDescriptorMap = Record<
string,
{ options: IDrawerScreenOptions; navigation: IDrawerNavigatorHandle }
>;

How you supply the content differs by framework, matching each one’s own idiom for “caller-supplied UI that needs live data”:

Framework Mechanism Description
React renderDrawerContent render prop A (props) => ReactNode function passed directly to <Drawer>
Vue drawerContent scoped slot <template #drawerContent="{ state, descriptors, navigation }"> in SFC, or a { drawerContent: (props) => ... } slots object in TSX
Angular TemplateRef via @ContentChild A projected <ng-template #drawerContent let-ctx> — the ref variable name must be exactly drawerContent, read via @ContentChild('drawerContent', { read: TemplateRef })
Svelte drawerContent Snippet prop {#snippet drawerContent(slot: IDrawerContentSlotProps)}...{/snippet} passed as the drawerContent prop on <Drawer>

If you skip the render prop/slot/template entirely, the panel slot renders empty — Drawer never falls back to a default menu.


See useNavigation()/useRoute()/useIsFocused()/useFocusEffect() (Angular: injectNavigation()/injectRoute()/injectIsFocused()/injectFocusEffect()) across all four adapters, including how to narrow a navigation handle down to IDrawerNavigatorHandle.