OverlayHost
The one layer where every Dialog, Menu and toast appears, above native sheets. Mount it once at the root; open overlays from anywhere.
Usage
Mount one OverlayHost at the root of your app, after your navigator and inside ThemeProvider:
import { OverlayHost, ThemeProvider, type Theme } from "@terratcg/ui";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export function Root({ theme }: { theme: Theme }) {
const insets = useSafeAreaInsets();
return (
<ThemeProvider theme={theme}>
<Navigator />
<OverlayHost
failureText="That didn't go through. Try again."
insets={insets}
toastInsetTop={insets.top}
/>
</ThemeProvider>
);
}Then open overlays from anywhere, like Alert.alert, with no state or component of your own:
const left = await showDialog({
title: "Leave the call?",
body: "The others stay connected.",
confirm: "Leave",
cancel: "Stay",
tone: "destructive",
});
if (left) navigation.goBack();showDialog, showMenu, showToast and showTopToast queue their overlay for the host. Each component's page covers its options.
Props
Prop
Type
What the host does
- Above everything: Dialogs and Menus are drawn in a
FullWindowOverlayon iOS and a transparentModalon Android, so they cover native sheets and headers. The layer is mounted only while something shows. - One at a time: Dialogs and Menus wait their turn. Opening a Dialog dismisses the keyboard.
- Toasts: one queue at the top of the window, one toast at a time. Toasts wait while a Dialog or Menu is up, or while
holdToastsis set; one already in view steps aside and comes back with its whole time. - Failures: when a Dialog's
onConfirmrejects, the Dialog stays open and showsfailureText, a sentence in the user's language. The error's own message is written for developers and never shown.onFailurehears every rejection, where you report it, and may return a sentence to show instead. - Theme and feedback: overlays wear the surrounding theme, or the host's
themewhen the screen in front wears another one. Their keys report to the surroundingfeedbackhandler. - Knowing one is up:
useOverlayUp()istruewhile a Dialog or Menu is up anywhere, the same signal that holds toasts. Use it to wait before showing something of your own, such as a system prompt. - Closing everything:
dismissOverlays()closes every open and queued overlay as if the user had backed out. Call it when a session ends or a screen that owns a pending action goes away.
Accessibility
- Focus: screen-reader focus moves to a Dialog's or Menu's title when it opens. Pass the control that opened it as
trigger, and focus returns there when it closes. - Back: Android Back cancels the Dialog or closes the Menu in front.
- Toasts: announced once when they first show. They never take screen-reader focus or block touches around their card.
Platform differences
- iOS: the layer is a
FullWindowOverlayfrom react-native-screens. In a native sheet, wrap the sheet's content inSheetOriginso a Menu is placed next to its trigger: iOS measures inside a sheet from the sheet's top. - Android: the layer is a transparent
Modalthat brings Back and focus with it.SheetOriginis a plain view there. - Tests: in Jest, mock
FullWindowOverlayfromreact-native-screenswith a plainView, since the test renderer has no native layer.
Rules
- Mount exactly one
OverlayHost, at the root, insideThemeProviderand after the navigator. - Never present a
Modalor a navigator from inside an overlay. - Navigate after
showDialogresolves, not insideonConfirm: a screen presented while the Dialog is up lands above it. - Write
failureTextin the user's language, as a neutral sentence. - Pass the safe area through
insetsandtoastInsetTop. TerraUI has no safe area of its own.