Backdrop component
Backdrop is the overlay primitive Modal and Drawer are both built on — a portaled, focus-trapping dim/blur layer behind arbitrary content. Reach for Modal or Drawer for their pre-built header/body/footer chrome; reach for Backdrop directly when you need the overlay behavior (stacking, focus trap, scroll lock, dismissal) around something with a fully custom layout.
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Backdrop actually gets used — each one a real, self-contained example.
Overlay primitive
Backdrop is the bare dimmed overlay behind Modal/Drawer — reach for it directly for a lightbox or a custom centered overlay. You own isOpen/onClose.
const [isOpen, setIsOpen] = useState(false);
<Button onPress={() => setIsOpen(true)}>Open overlay</Button>
<Backdrop isOpen={isOpen} onClose={() => setIsOpen(false)}>
<div className="rounded-lg bg-paper p-6">
<p>Click outside or press Escape to close.</p>
</div>
</Backdrop>Blur and opacity
blur frosts the content behind the overlay; backgroundOpacity controls how dark the scrim is.
<Backdrop
isOpen={isOpen}
onClose={onClose}
blur="sm"
backgroundOpacity={60}
>
{content}
</Backdrop>Entrance animation
animationType controls how the overlay and its content enter and exit.
<Backdrop
isOpen={isOpen}
onClose={onClose}
animationType="zoom"
animationDuration={0.2}
>
{content}
</Backdrop>Custom overlay content
Same controlled shape as Modal/Drawer — isOpen and onClose are both required, and children is whatever you want rendered above the dimmed backdrop, with total control over its layout instead of Modal's fixed header/body/footer slots.
const ImageLightbox = ({ src, isOpen, onClose }: { src: string; isOpen: boolean; onClose: () => void }) => {
return (
<Backdrop isOpen={isOpen} onClose={onClose} blur="lg">
<img src={src} alt="" className="max-h-[90vh] max-w-[90vw] rounded-lg" />
</Backdrop>
);
};Blur, tint, and opacity
blur accepts true (a sensible default), a size token, or a raw pixel number for the backdrop-filter blur amount. backgroundColor and backgroundOpacity control the dim layer's tint independently of the blur, for anything from a near-black scrim to a subtle, barely-there overlay.
<Backdrop isOpen={isOpen} onClose={onClose} blur="sm" backgroundOpacity={60}>
{content}
</Backdrop>Entrance animation
animationType picks how the backdrop and its content enter/exit (fade, zoom, or a directional slide), with animationDuration/easing to fine-tune the feel — the same animation system Modal and Drawer forward their own animation props into internally.
<Backdrop isOpen={isOpen} onClose={onClose} animationType="zoom" animationDuration={0.2}>
{content}
</Backdrop>Full-size content
By default the content wrapper sizes to its content and centers within the backdrop. Set contentFullSize to stretch it to fill the backdrop instead — useful for a custom overlay that needs to manage its own internal layout/scrolling edge to edge, like a full-viewport image gallery.
Props
The full public API for Backdrop. Every prop is stable and intentionally typed.
Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Content rendered above the backdrop. |
children
ReactNode
default: —
Content rendered above the backdrop.
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| blur | boolean | "sm" | "md" | "lg" | number | true (8px) | Backdrop blur amount. |
| backgroundColor | string | "var(--oks-palette-neutral-950, #000)" | Backdrop tint color. |
| backgroundOpacity | number | 80 | Backdrop opacity (0-100). |
blur
boolean | "sm" | "md" | "lg" | number
default: true (8px)
Backdrop blur amount.
backgroundColor
string
default: "var(--oks-palette-neutral-950, #000)"
Backdrop tint color.
backgroundOpacity
number
default: 80
Backdrop opacity (0-100).
Layout & positioning
| Prop | Type | Default | Description |
|---|---|---|---|
| zIndex | number | — | Explicit stacking z-index; auto-computed per overlay depth when unset. |
| portal | boolean | true | Renders through a React portal. |
| container | Element | DocumentFragment | null | — | Custom portal mount target. |
| contentFullSize | boolean | false | Stretches the content wrapper to fill the backdrop. |
zIndex
number
default: —
Explicit stacking z-index; auto-computed per overlay depth when unset.
portal
boolean
default: true
Renders through a React portal.
container
Element | DocumentFragment | null
default: —
Custom portal mount target.
contentFullSize
boolean
default: false
Stretches the content wrapper to fill the backdrop.
Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen | boolean | — | Controls whether the backdrop is open (required). |
| closeOnOutsideClick | boolean | true | Closes when clicking outside the content. |
| closeOnEscape | boolean | true | Closes on Escape. |
| lockScroll | boolean | true | Locks body scroll while open. |
| trapFocus | boolean | true | Traps keyboard focus within the content while open. |
isOpen
boolean
default: —
Controls whether the backdrop is open (required).
closeOnOutsideClick
boolean
default: true
Closes when clicking outside the content.
closeOnEscape
boolean
default: true
Closes on Escape.
lockScroll
boolean
default: true
Locks body scroll while open.
trapFocus
boolean
default: true
Traps keyboard focus within the content while open.
Motion
| Prop | Type | Default | Description |
|---|---|---|---|
| animationDuration | number | 0.3 | Enter/exit animation duration in seconds. |
| animationType | "fade" | "zoom" | "slideUp" | "slideDown" | "slideLeft" | "slideRight" | fade | Entrance/exit animation. |
| easing | "ease" | "easeIn" | "easeOut" | "easeInOut" | ease | Animation easing curve. |
animationDuration
number
default: 0.3
Enter/exit animation duration in seconds.
animationType
"fade" | "zoom" | "slideUp" | "slideDown" | "slideLeft" | "slideRight"
default: fade
Entrance/exit animation.
easing
"ease" | "easeIn" | "easeOut" | "easeInOut"
default: ease
Animation easing curve.
Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onClose | () => void | — | Called to close the backdrop (required). |
onClose
() => void
default: —
Called to close the backdrop (required).
Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Class applied to the backdrop layer. |
| style | CSSProperties | — | Inline styles for the backdrop layer. |
| contentClassName | string | — | Class applied to the content wrapper. |
| contentStyle | CSSProperties | — | Inline styles for the content wrapper. |
className
string
default: —
Class applied to the backdrop layer.
style
CSSProperties
default: —
Inline styles for the backdrop layer.
contentClassName
string
default: —
Class applied to the content wrapper.
contentStyle
CSSProperties
default: —
Inline styles for the content wrapper.
Accessibility
- Traps focus inside children while open and restores it to whatever triggered the backdrop on close, the same focus-trap behavior Modal and Drawer inherit from it.
- lockScroll (on by default) prevents the page behind the backdrop from scrolling while it's open, keeping the user's context stable.
- closeOnEscape and closeOnOutsideClick (both on by default) give keyboard and mouse users independent, standard ways to dismiss the overlay without you having to wire either up manually.
Best practices
- Reach for Modal or Drawer first — they're Backdrop plus a well-tested header/body/footer layout. Drop to Backdrop directly only when your content genuinely doesn't fit that shape (a lightbox, a custom full-screen takeover).
- Keep trapFocus on unless you have a specific reason to disable it — turning it off means keyboard focus can silently leave the overlay while it's still open, which is rarely what you want.
- Use contentFullSize sparingly — most overlay content reads better centered and sized to itself; full-size is for genuinely edge-to-edge experiences, not a way to avoid sizing your content properly.
- When building a custom overlay on Backdrop, keep an explicit dismiss control inside children too (not just Escape/outside-click) — a fully custom layout can otherwise leave touch or assistive-tech users without an obvious way to close it.