Overlay
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Overlay

Shared backdrop for everything that covers the screen.

Photos
IMG_0142
Share
Duplicate
Add to album
Delete
Delete this photo?
It will be removed from all your devices.
Cancel
Delete
<Overlay visible={false} onPress={close} />

Pick a component, then press the dimmed area to close it. The backdrop is the same in all three cases. Only the content above it changes.

Installation

npx axiom add overlay
pnpm dlx axiom add overlay
yarn dlx axiom add overlay
bun x axiom add overlay

Installs: react-native-reanimated, react-native-worklets.

What it does

Overlay is the backdrop behind sheets, dialogs and menus. It dims the screen, fades in and out, and catches presses outside the content. Each overlay component renders one instead of drawing its own, so a sheet and a dialog dim the screen with the same color, opacity and timing.

You rarely render it yourself. BottomSheet, Menu and Dialog already do. Use it directly when you build your own overlay component.

Usage

import { Overlay } from "@/components/core/overlay";
import { Portal } from "@/components/core/portal";

function Popover({ open, onClose, children }) {
	return (
		<Portal>
			<Overlay visible={open} onPress={onClose} />
			{open && <PopoverContent>{children}</PopoverContent>}
		</Portal>
	);
}

Overlay fills its parent with StyleSheet.absoluteFill. Put it in a Portal so the parent is the PortalHost, which covers the whole screen. Render the content after it, so the content sits on top.

Layers

  1. 4ContentBottomSheet.Content, Menu.Content…
  2. 3Overlaydims and catches presses
  3. 2PortalHostfills the screen, empty = no touches
  4. 1Screenyour navigation stack

An open sheet stacks four layers. Your screen is at the bottom. The PortalHost fills the screen above it and lets touches through while empty. Overlay comes next, then the sheet content.

A press on the content goes to the content. A press anywhere else lands on Overlay, which calls onPress. Nothing reaches the screen while the overlay is visible.

Animation

visible drives the animation.

StepWhat happens
visible becomes trueOverlay mounts and fades from 0 to opacity in duration ms. It catches presses right away.
visible becomes falseIt stops catching presses, then fades back to 0.
Fade out endsonExited fires and Overlay renders nothing.

Touches go back to the screen as soon as closing starts. A user who taps twice quickly reaches the list behind instead of hitting a backdrop that is still fading.

Keep the content mounted until onExited if it has its own exit animation, so both finish together:

const [mounted, setMounted] = useState(open);
if (open && !mounted) setMounted(true);

<Overlay visible={open} onPress={onClose} onExited={() => setMounted(false)} />;
{
	mounted && <DialogContent visible={open} />;
}

With Reduce Motion turned on in the system settings, there is no fade. The overlay appears and disappears at once, and onExited fires right after visible becomes false.

Following a gesture

Photos
IMG_0142
Share
Duplicate
Add to album
Delete
Delete this photo?
It will be removed from all your devices.
Cancel
Delete
progress 1.00 → opacity 0.40

Move the slider. The backdrop gets darker as the sheet rises.

A sheet the user drags shouldn’t fade on a timer. Pass progress, a Reanimated shared value from 0 (closed) to 1 (open), and the opacity follows it on the UI thread, with no React render per frame:

const progress = useDerivedValue(() =>
	interpolate(translateY.value, [closedY, openY], [0, 1], Extrapolation.CLAMP),
);

<Overlay visible={open} progress={progress} onPress={close} />;

With progress, visible still decides whether the overlay catches presses, but duration is ignored. BottomSheet works this way.

Dismissal

Without onPress, the overlay still blocks the screen but a press does nothing. Use it for a flow the user must finish, like a required choice or an action in progress:

<Overlay visible={saving} />

Overlay doesn’t handle the Android back button. The component that owns the overlay decides what back does, since a menu, a sheet with unsaved input and a blocking dialog each need something different.

Color and dark mode

The default is black at 0.4 opacity, in both color schemes. Switch the first preview to Dark: the backdrop is barely visible on a black screen. That’s expected. In dark mode the content stands out through background.elevated, which is lighter than the screen, not through the backdrop.

Change color and opacity only when a design calls for it. Changing them on one component makes it look different from the others, which is the thing Overlay exists to avoid.

Props

PropTypeDefaultDescription
visiblebooleanShows or hides the overlay, with the enter or exit animation.
onPress() => voidCalled when the overlay is pressed. Without it, presses are blocked and ignored.
onExited() => voidCalled when the exit animation ends.
progressSharedValue<number>Drives opacity from 0 to 1 instead of the timed animation.
opacitynumber0.4Opacity when fully visible.
colorColorValue'hsla(0, 0%, 0%, 1)'Backdrop color.
durationnumber250Fade duration in ms. Ignored with progress.
accessibilityLabelstring'Close'Label read by screen readers when onPress is set.
styleStyleProp<ViewStyle>Merged after StyleSheet.absoluteFill.

Accessibility

  • With onPress, the overlay is a button labeled accessibilityLabel. VoiceOver and TalkBack users can reach it and close the overlay.
  • On iOS, the two-finger scrub gesture (onAccessibilityEscape) calls onPress too.
  • Without onPress, screen readers skip the overlay.
  • Overlay doesn’t hide the screen behind it from screen readers. The content does that, with accessibilityViewIsModal on iOS. BottomSheet, Menu and Dialog set it for you. Set it yourself on a custom overlay component.

Used by