Components / Utilities

Overlay

Overlay is a full-viewport scrim. It composes Portal with a fixed-position Box, so the backdrop covers everything regardless of where the call site sits in the tree.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A Portal wrapping a position: fixed Box that fills the viewport, tinted with a translucent scrim and centring its children. Because it portals, no ancestor's overflow or transform can clip it — the scrim genuinely covers the page.

Overlay is a layout building block, not a dialog. It draws the backdrop and reports a click on the scrim itself; it does not trap focus, handle Escape, or make the background inert. Compose it with FocusScope for the full modal contract.

Install

Overlay is exported from usemotif. No separate install.

example.tsx
import { Overlay } from 'usemotif';

API

Overlay

stable
function Overlay(props: OverlayProps): JSX.Element
onScrimClick() => void

Fired when the user clicks the scrim itself — not a click on the children. Wire it to a tap-outside-to-dismiss handler.

scrimstring= "rgba(0, 0, 0, 0.5)"

The backdrop tint. Any CSS colour.

childrenReactNode

Content centred on the scrim — typically the dialog surface.

…BoxPropsOmit<BoxProps, "position">

Every Box prop except `position`, which Overlay manages.

Accessibility

onScrimClick fires only for a click on the scrim, not on the content — so tap-outside-to-dismiss does not trigger when the user clicks inside the dialog. Pair it with an Escape handler so the overlay is dismissible by keyboard as well as pointer; onScrimClick alone leaves keyboard users without a way out.

The scrim dims the background but does not disable it. On its own, a screen reader still reaches the content underneath and Tab still moves into it. The full modal contract — background inert, focus trapped, Escape wired — comes from composing Overlay with FocusScope, which is what the headless Dialog does.

Examples

A scrim with tap-outside dismissal:

example.tsx
<Overlay onScrimClick={close}>
<Box bg="$colors.surface.base" p="$6" borderRadius="$radii.lg">
  {dialogContent}
</Box>
</Overlay>

A complete modal — Overlay plus FocusScope:

example.tsx
<Overlay onScrimClick={close}>
<FocusScope onEscape={close}>
  <Box bg="$colors.surface.base" p="$6" borderRadius="$radii.lg">
    <Heading>Delete project</Heading>
    <Text>This cannot be undone.</Text>
    <HStack gap="$2">
      <Button intent="neutral" onPress={close}>Cancel</Button>
      <Button intent="danger" onPress={confirm}>Delete</Button>
    </HStack>
  </Box>
</FocusScope>
</Overlay>