Sheet
A bottom sheet that animates
Sheet presents draggable content at configurable snap points and can host adaptive content from other overlay components.
Features
Lightweight implementation with drag support
Multiple snap points and a handle
Automatic screen-size adaptation
Animations, themes, and size props
Sheet is a bottom panel for mobile-friendly dialogs and action menus. It supports drag-to-dismiss, multiple snap points, and automatically stacks above other content.
Installation
Sheet is already installed in tamagui, or you can install it independently:
yarn
npm
bun
pnpm
For native apps, we recommend setting up native portals to preserve React context inside Sheet content.
Anatomy
API reference
Sheet
Contains every component for the sheet.
Props
open
boolean
Set to use as controlled component.
scope
string
Isolates this Sheet and its parts from other Sheet instances when needed.
defaultOpen
boolean
Uncontrolled open state on mount.
onOpenChange
(open: boolean) => void
Called on change open, controlled or uncontrolled.
position
number
Controlled position, set to an index of snapPoints.
defaultPosition
number
Uncontrolled default position on mount.
snapPoints
(string | number)[]
Default:
[80]Array of pixels or percents the sheet moves to when dragged. The first is the topmost and default when first opened. Use "open" prop for fully closed.
onPositionChange
(position: number) => void
Called on change position, controlled or uncontrolled.
dismissOnOverlayPress
boolean
Default:
trueControls tapping on the overlay to close, defaults to true.
transitionConfig
AnimatedNumberStrategy
Customize the animation strategy for position transitions.
native
boolean | "ios"[]
(iOS only) Render with the system renderer registered through setupNativeSheet. UIKit does not expose continuous position, so Sheet.useAnimatedPosition() and onTransition are unavailable in this mode.
disableDrag
boolean
Disables all touch events to drag the sheet.
modal
boolean
Renders sheet into the root of your app instead of inline.
dismissOnSnapToBottom
boolean
Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).
disableRemoveScroll
boolean
Default:
falseDisables the RemoveScroll behavior that prevents body scrolling while sheet is open. By default, RemoveScroll is enabled when the sheet is open and modal.
portalProps
PortalProps
Props passed to the Portal that sheet uses when in modal mode.
moveOnKeyboardChange
boolean
Default:
falseMakes the sheet move up when the mobile keyboard opens so the focused input remains visible. Works on native and on mobile web.
preferAdaptParentOpenState
boolean
Default:
falseBy default Sheet will prefer the open prop over a parent component that is controlling it via Adapt. In general if you want to Adapt to a sheet, you'd leave the open prop undefined. If you'd like to have the parent override the prop you've set manually on Sheet, set this to true.
onTransition
(e: { phase: 'start' | 'end', cause: 'open' | 'close' | 'snap', position: number, finished?: boolean }) => void
Fires at the start and end of the custom sheet's position transition. cause is "open" when moving from closed, "close" when moving off screen, and "snap" when moving between snap points while open. On the end phase, finished is false when the transition was interrupted (e.g. a close canceled by a re-open). position is the resolved translateY target in px from the top of the screen. Replaces the old onAnimationComplete prop. The native iOS system sheet does not expose this lifecycle.
disableHideWhenClosed
boolean
Default:
falseBy default a fully-closed sheet wrapper is hidden with display: "none". Set this to keep the closed wrapper laid out, for example so native visual effects can initialize below it. pointerEvents still gates interaction while closed.
unmountChildrenWhenHidden
boolean
Default:
false(experimental) Remove the children while the sheet is fully closed to save some rendering cost. Can interfere with animations.
Sheet.Overlay
Displays behind the sheet content. Extends YStack.
Sheet.Overlay must be a direct child of Sheet. It renders outside the animated
content region so it stays fixed while the container moves.
In v3 the sheet no longer fades anything for you. Sheet.Overlay has no baked-in
opacity, so you drive its fade yourself. See Overlay fades for
the two supported patterns.
Sheet.Container
Contains the sheet content and layout props. Extends YStack.
Props
adjustPaddingForOffscreenContent
boolean
Adds padding for currently offscreen content so flex children size to the visible sheet area.
Sheet.Background
The themed sheet surface. Extends YStack.
Place it as the first child of Sheet.Container. By default it fills the
container and extends past the bottom edge so spring overshoot does not reveal
the page below the sheet. Put visual surface props like bg, borderRadius,
and shadow* on Sheet.Background; keep layout props like
padding, gap, and maxHeight on Sheet.Container.
Props
disableHideBottomOverflow
boolean
Disables the default background extension below the sheet.
Sheet.Handle
Shows a handle above the container by default. On tap, it will cycle between
snapPoints, but this can be overridden with onPress.
Extends XStack.
In v3 the Handle ships no opacity of its own. The idle dim and the fade-in on
open are aesthetics, so they live in the copied skin rather than the behavior
package. Set opacity (and an open-driven opacity if you want it) on your own
Sheet.Handle.
Sheet.ScrollView
Allows scrolling within Sheet. Extends ScrollView.
Sheet.useAnimatedPosition
Sheet.useAnimatedPosition() returns the sheet’s live animated position so you
can drive effects that track the drag on the UI thread. Call it inside a Sheet;
it throws with a clear message outside that scope.
valueis the exactUniversalAnimatedNumberdriving the frame’s translateY, in px from the top of the screen. Feed it to useAnimatedNumberStyle.screenSizeis the height of the viewport the sheet positions against.frameSizeis the measured height of the sheet frame.snapOffsetsare the resolved translateY positions, in the same order assnapPoints.minYis the top-most (fully open) position.
These are enough to compute any progress mapping inside a getStyle worklet.
There is no separate derived progress value; the recipe lives in docs and the
canonical skin.
Sheet.useAnimatedPosition() and the Sheet onTransition callback are available
on Tamagui’s custom Sheet. The native iOS system sheet selected by the native
prop is driven by UIKit, which does not expose a continuous position. Remove the
native prop when you need position-linked effects or transition lifecycle events.
Overlay fades
The sheet does not fade anything for you, so an overlay fade is your code. There are two supported patterns.
1. Presence fade (animates on open and close only). Put enter/exit styles on
Sheet.Overlay:
2. Drag-linked fade (tracks the finger). Read the animated position and map
it to opacity with useAnimatedNumberStyle:
Keep Sheet.Overlay as the direct child of Sheet, then render the hook-driven
backdrop inside it. The driver’s animated view makes the worklet-driven style
apply on every driver.
Native system renderers
Register a renderer at app startup to use system presentation with <Sheet native>.
Without a registered renderer, Sheet uses its custom implementation. Tamagui
owns open and position state, context, Adapt, and the lifetime of one content
tree. The renderer owns platform presentation, authored detents, gestures,
keyboard handling, and physical dismissal.
NativeSheetRendererProps supplies the resolved open value, onOpenChange,
position, onPositionChange, and normalized snapPoints. A point is
{ type: 'percent', value: number }, { type: 'height', value: number }, or
{ type: 'fit' }. A dismissal-only bottom point is removed before forwarding.
The other Sheet props, content, and host ref reach the renderer unchanged.
Report interaction requests through onOpenChange and onPositionChange.
Controlled parents may decline either request; continue presenting their
accepted values. Call onDismiss only after the original presentation is
physically removed following an accepted close. A reopen interrupts that
completion. Tamagui then releases Adapt presence and, when requested, unmounts
hidden children. Do not mount a second copy of the content beside the native
presentation.
Render children once inside the platform host. Sheet.Container measures
content without custom sheet positioning, Sheet.ScrollView forwards to the
native React Native ScrollView, and the system supplies the background,
overlay, and grabber. Sheet.Background, Sheet.Overlay, and Sheet.Handle
render nothing in this mode. Their styles and press handlers do not customize
the system chrome. Use the renderer’s presentation callbacks for dismissal
policy.
The registration accepts a React component. An imperative implementation keeps
a controller ref inside that component and drives its presentation methods from
the resolved open value. Use a layout effect when these commands synchronize
the host’s controlled value, so its acknowledgment observes the parent’s accepted
state. Forward interaction requests and physical completion separately. Preserve
the native library’s content container and lazy mounting behavior, and check
parent-declined close and reopen during dismissal against its actual events.
The previous setupNativeSheet('ios', modalModule) object registration is
replaced by this component contract; no modal dependency is built into Tamagui.
Native renderers decide which Sheet options their platform can represent.
Reject unsupported combinations before presenting. A native iOS system sheet
does not expose continuous animated position or custom transition events.
moveOnKeyboardChange does not install Tamagui’s custom keyboard driver in a
native renderer; its host handles the keyboard. Keep native off for custom
sheet animation and gesture behavior.
Native gesture handler integration
For the best gesture experience on iOS and Android, Sheet supports optional integration with react-native-gesture-handler. This provides:
- Smooth scroll-to-drag handoffs - transition between scrolling content and dragging the sheet
- No gesture conflicts - Sheet and ScrollView gestures coordinate properly
- Native-quality feel - matches the behavior of system sheets
Setup
- Install
react-native-gesture-handler:
yarn
npm
bun
pnpm
- Add the setup import to your app entry point (before any Tamagui imports):
That’s it! Sheet will automatically detect and use the native gesture handler when available.
Using Sheet.ScrollView
When using scrollable content inside a Sheet, use Sheet.ScrollView for proper gesture coordination:
This ensures:
- Scrolling up at the top of content works naturally
- Dragging down when scroll is at top drags the sheet
- Direction changes mid-gesture work smoothly
Without gesture handler
If you don’t set up react-native-gesture-handler, Sheet falls back to React Native’s built-in PanResponder. This works well for basic use cases but has some limitations on iOS where scroll and pan gestures can occasionally conflict.
Notes
A fully-closed sheet is hidden with display: 'none' rather than by fading to
transparent. Use disableHideWhenClosed if you need the closed
wrapper to stay laid out. pointerEvents still gates interaction while closed,
and unmountChildrenWhenHidden keys off the same closed state.
For Android you need to manually re-propagate any context when using modal.
This is because React Native doesn’t support portals yet.
Source
v2-look Sheet: styled handle (open/closed opacity), dimmed overlay, rounded background, padded container and scroll view, plus the controlled composition, over the unstyled @tamagui/ui Sheet behavior. This is the exact source the registry ships — copy it into your app and the examples above import from your copy.
components/tamagui/Sheet.tsxDependencies
yarn add @tamagui/core @tamagui/sheetExpects theme tokens: background, color-5, shadow-color. Native: requires a Portal provider at the app root for the sheet to mount above content react-native-safe-area-context is used for safe-area insets on native (optional peer)
Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).