Marble UI
← Components

Overlay / Modals Manager

Preview: Default

Mount once. Lets any code open dialogs imperatively and await their result (confirm, prompt, custom).

Import

import { OverlayProvider } from '@marbleui/ui'
import { useOverlay } from '@marbleui/ui'

Props

<OverlayProvider>

PropTypeDefaultDescription
children *requiredReactNode—

Hooks

useOverlay(): { open: <T>(render: (close: (v?: T | undefined) => void) => ReactNode) => Promise<T | undefined>; confirm: (title: string, body?: str…

`confirm()` / `prompt()` that return promises, plus raw `open()` for custom dialogs.

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Heading · React Aria Modal · React Aria ModalOverlay · React Aria Dialog. Follow the primitive's docs for the full keyboard map.

Usage notes

Open dialogs from event handlers and await the answer: confirm(), prompt(), and open() for custom content. Mount OverlayProvider once near the app root. For a confirmation tied to a single button, AlertDialog or Popconfirm is simpler.

const { confirm, prompt } = useOverlay()
if (await confirm('Delete 3 files?', 'They move to the trash for 30 days.', { confirmLabel: 'Delete', destructive: true })) deleteFiles()
const name = await prompt('Rename project', 'Project name', project.name)
  • confirm resolves true or false; Escape and clicking outside count as cancel. It is an alertdialog with focus on Cancel.
  • prompt resolves the entered text, or undefined when cancelled. Enter submits.
  • open((close) => …) renders your content in a bare modal and resolves with whatever you pass to close. Wrap that content in React Aria’s Dialog with a Heading slot="title", as confirm and prompt do, so it’s a labelled dialog.
  • useOverlay() throws outside an OverlayProvider.
Example source · show
import { useState } from 'react'
import { Button } from '../button'
import { OverlayProvider, useOverlay } from './overlay-manager'

function Inner() {
  const { confirm, prompt } = useOverlay()
  const [log, setLog] = useState<string[]>([])
  const add = (s: string) => setLog((l) => [s, ...l].slice(0, 4))
  return (
    <div className="flex w-80 flex-col gap-3">
      <div className="flex gap-2">
        <Button variant="outline" onPress={async () => add((await confirm('Delete 3 files?', 'They move to the trash for 30 days.', { confirmLabel: 'Delete', destructive: true })) ? 'Deleted' : 'Kept files')}>confirm()</Button>
        <Button variant="outline" onPress={async () => { const name = await prompt('Rename project', 'Project name', 'Website'); add(name ? `Renamed to “${name}”` : 'Rename cancelled') }}>prompt()</Button>
      </div>
      <ul aria-live="polite" className="text-sm text-fg-muted">{log.map((l, i) => <li key={i}>→ {l}</li>)}</ul>
    </div>
  )
}
export const styles = { Default: () => <OverlayProvider><Inner /></OverlayProvider> }

Source: packages/ui/src/overlay-manager/overlay-manager.tsx

Catalog

Category
utilities
Priority
Extra
Primitive
custom