ComponentsOverlayDialog

Dialog

5 types · 4 positions · 5 sizes · form · image header · multi-step · command palette

Alert typesFormImage headerBottom drawerRight panelFullscreenMulti-stepCommand palette

Overview

Dialog interrupts the current page to focus attention on a decision, short task, or critical information that must be addressed before returning.

Focused task

Live preview

A clear trigger opens a dialog with a specific title, concise body, and outcome-based actions.

WelcomeDialog.tsx
tsx
import { Dialog, Button } from 'omverse-ui'
 
{/* Default */}
<Dialog
open={basic}
onClose={() => setBasic(false)}
size="sm"
showCloseButton
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setBasic(false)}>Dismiss</Button>
<Button variant="filled" style={{ flex: 1 }} onClick={() => setBasic(false)}>Get started</Button>
</div>
}
>
<p>Welcome back, John!</p>
</Dialog>
 
{/* Destructive */}
<Dialog
open={destructive}
onClose={() => setDestructive(false)}
type="destructive"
size="sm"
showCloseButton={false}
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setDestructive(false)}>Cancel</Button>
<Button variant="filled" color="error" style={{ flex: 1 }} onClick={() => setDestructive(false)}>Yes, delete account</Button>
</div>
}
>
<p>This action cannot be undone.</p>
</Dialog>
 
{/* Success */}
<Dialog
open={success}
onClose={() => setSuccess(false)}
type="success"
size="xs"
showCloseButton={false}
footer={
<Button variant="filled" color="success" style={{ width: '100%' }} onClick={() => setSuccess(false)}>
Continue
</Button>
}
>
<p>Payment of $49.00 processed successfully.</p>
</Dialog>
 
{/* Warning */}
<Dialog
open={warning}
onClose={() => setWarning(false)}
type="warning"
size="xs"
showCloseButton={false}
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setWarning(false)}>Stay on page</Button>
<Button variant="filled" color="warning" style={{ flex: 1 }} onClick={() => setWarning(false)}>Leave anyway</Button>
</div>
}
>
<p>You have unsaved changes that will be lost.</p>
</Dialog>

Anatomy

A dialog combines a modal backdrop, labelled panel, optional media or status icon, body content, close affordance, and footer actions.

Delete workspace?×
This action permanently deletes the workspace.
1234
  1. 1
    BackdropRequired

    Separates the modal task from the inactive page.

  2. 2
    HeaderRequired

    Provides the accessible title, optional subtitle, and close action.

  3. 3
    BodyRequired

    Contains the message, form, or focused task content.

  4. 4
    Footer

    Groups primary and secondary outcomes in a predictable order.

When to use

Use a dialog when the current workflow cannot safely continue without a response or focused subtask.

Recommended

  • Confirm consequential actions

    Ask for confirmation when an action is destructive, costly, or difficult to reverse.

  • Complete a short focused task

    Use for compact forms or decisions that preserve the underlying page context.

  • Communicate blocking information

    Use when people must acknowledge or resolve a condition before continuing.

When not to use

Avoid modal interruption for information or work that belongs in the normal page flow.

Avoid

  • Do not use for passive status

    Use Alert, Toast, or inline feedback when no immediate decision is required.

  • Do not use for long workflows

    Use a dedicated page when the task contains many fields, steps, or reference material.

  • Do not stack dialogs

    Close or replace the current dialog before opening another modal surface.

Variants

Type communicates intent; position and size adapt the surface to the amount and structure of content.

Centered

Default for confirmations, alerts, and short forms.

Bottom drawer

Supports touch-oriented choices or compact mobile tasks.

Side panel

Supports contextual detail or editing with more vertical space.

Intent types

Default, destructive, success, warning, and info reinforce the message.

States

Dialog state includes entry, focus containment, validation or progress inside the task, and a clear exit path.

StateTriggerVisual responseInteraction
ClosedNo modal task is activeUnderlying page is availableTrigger can open the dialog
Openingopen becomes trueBackdrop and panel enterInitial focus moves inside
OpenPanel is activePage is visually de-emphasizedFocus remains within the dialog
ValidationSubmitted content is invalidInline field feedbackDialog stays open for correction
SubmittingAsync action is runningProgress and disabled duplicate actionDismissal follows the operation policy
ClosingAction, close, Escape, or backdropPanel exitsFocus returns to the opener

Behavior

Opening a dialog moves focus into it, locks background scrolling, and prevents interaction with the underlying page until dismissal.

Initial focus

Focus the safest useful control; avoid placing focus on a destructive primary action.

Focus containment

Tab and Shift+Tab cycle through controls inside the active panel.

Dismissal

Escape, backdrop, close button, and actions follow the configured dismissal policy.

Focus return

After close, restore focus to the element that opened the dialog.

Accessibility

Dialog exposes modal semantics, an accessible name, an optional description, contained focus, and predictable keyboard dismissal.

KeyAction
TabMoves to the next focusable control within the dialog.
ShiftTabMoves to the previous focusable control within the dialog.
EscCloses the dialog when closeOnEscape is enabled.
EnterSpaceActivates the focused action.
  • Provide a concise title for every dialog.
  • Keep focus inside while the dialog is open.
  • Return focus to the opening control after close.
  • Do not rely on backdrop click as the only dismissal method.
  • Keep destructive and cancel actions clearly distinguishable.
  • Describe meaningful hero images and leave decorative images with empty alt text.

Content guidelines

Dialog copy should explain why attention is required and what each available outcome will do.

State the task

Use a short title that names the decision or outcome.

ExampleDelete workspace?

Explain consequences

Put essential context and irreversible effects in the body.

ExampleThis permanently deletes 14 projects.

Label outcomes

Use specific verbs for primary actions and plain “Cancel” for the safe exit.

ExampleDelete workspace

Avoid redundant close choices

Do not present multiple actions that produce the same result unless the platform convention requires them.

ExampleCancel

Examples

Examples cover alert intent, form and image composition, alternative positions, multi-step tasks, and command-palette behavior.

Alert types

Live preview

type prop sets the icon and color scheme — default, destructive, success, warning

App.tsx
tsx
import { Dialog, Button } from 'omverse-ui'
 
{/* Default */}
<Dialog
open={basic}
onClose={() => setBasic(false)}
size="sm"
showCloseButton
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setBasic(false)}>Dismiss</Button>
<Button variant="filled" style={{ flex: 1 }} onClick={() => setBasic(false)}>Get started</Button>
</div>
}
>
<p>Welcome back, John!</p>
</Dialog>
 
{/* Destructive */}
<Dialog
open={destructive}
onClose={() => setDestructive(false)}
type="destructive"
size="sm"
showCloseButton={false}
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setDestructive(false)}>Cancel</Button>
<Button variant="filled" color="error" style={{ flex: 1 }} onClick={() => setDestructive(false)}>Yes, delete account</Button>
</div>
}
>
<p>This action cannot be undone.</p>
</Dialog>
 
{/* Success */}
<Dialog
open={success}
onClose={() => setSuccess(false)}
type="success"
size="xs"
showCloseButton={false}
footer={
<Button variant="filled" color="success" style={{ width: '100%' }} onClick={() => setSuccess(false)}>
Continue
</Button>
}
>
<p>Payment of $49.00 processed successfully.</p>
</Dialog>
 
{/* Warning */}
<Dialog
open={warning}
onClose={() => setWarning(false)}
type="warning"
size="xs"
showCloseButton={false}
footer={
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
<Button variant="outlined" style={{ flex: 1 }} onClick={() => setWarning(false)}>Stay on page</Button>
<Button variant="filled" color="warning" style={{ flex: 1 }} onClick={() => setWarning(false)}>Leave anyway</Button>
</div>
}
>
<p>You have unsaved changes that will be lost.</p>
</Dialog>

Form + image header

Live preview

Form dialog with Input fields; imageSrc places a hero banner at the top

App.tsx
tsx
import { Dialog, Button, Input } from 'omverse-ui'
 
{/* Form dialog */}
<Dialog
open={form}
onClose={() => setForm(false)}
title="Invite team member"
subtitle="They'll receive an email invitation"
size="sm"
footer={
<>
<Button variant="outlined" onClick={() => setForm(false)}>Cancel</Button>
<Button variant="filled" onClick={() => setForm(false)}>Send invite</Button>
</>
}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '16px 24px' }}>
<Input label="Email address" placeholder="colleague@company.com" leadingIcon="mail" />
<Input label="Full name" placeholder="John Doe" />
</div>
</Dialog>
 
{/* Image header dialog */}
<Dialog
open={image}
onClose={() => setImage(false)}
imageSrc="https://picsum.photos/seed/upgrade/600/160"
imageAlt="Upgrade to Pro"
title="Upgrade to Pro"
subtitle="Unlock all features"
size="sm"
footer={
<>
<Button variant="text" onClick={() => setImage(false)}>Maybe later</Button>
<Button variant="filled" onClick={() => setImage(false)}>Upgrade now</Button>
</>
}
>
<p style={{ padding: '16px 24px', fontSize: 14, color: 'var(--color-text-secondary)' }}>
Get unlimited projects, priority support, advanced analytics, and custom domains with Pro.
</p>
</Dialog>

Positions

Live preview

bottom = bottom drawer · right = side panel · size=fullscreen fills the viewport

App.tsx
tsx
{/* Bottom drawer */}
<Dialog
open={bottom}
onClose={() => setBottom(false)}
position="bottom"
title="Share document"
subtitle="Choose how to share"
footer={
<Button variant="outlined" style={{ width: '100%' }} onClick={() => setBottom(false)}>Cancel</Button>
}
>
<div style={{ padding: '16px 24px', display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
{['Copy link', 'Email', 'Message', 'WhatsApp', 'Twitter', 'LinkedIn'].map(label => (
<button key={label} onClick={() => setBottom(false)}
style={{ padding: '12px 8px', borderRadius: 12, border: '1px solid var(--color-border)',
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>{label}</span>
</button>
))}
</div>
</Dialog>
 
{/* Right panel */}
<Dialog
open={right}
onClose={() => setRight(false)}
position="right"
title="Edit profile"
subtitle="Update your information"
footer={
<>
<Button variant="outlined" onClick={() => setRight(false)}>Cancel</Button>
<Button variant="filled" onClick={() => setRight(false)}>Save changes</Button>
</>
}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '16px 24px' }}>
<Input label="Full name" defaultValue="John Doe" />
<Input label="Role" defaultValue="Product Designer" />
<Input label="Email" defaultValue="john@example.com" leadingIcon="mail" />
</div>
</Dialog>
 
{/* Fullscreen — size="fullscreen" with position="center" */}
<Dialog
open={fullscreen}
onClose={() => setFullscreen(false)}
position="center"
size="fullscreen"
title="Full screen dialog"
subtitle="Takes up the entire screen"
footer={
<Button variant="outlined" onClick={() => setFullscreen(false)}>Close</Button>
}
>
<p style={{ padding: '16px 24px', fontSize: 14, color: 'var(--color-text-secondary)' }}>
Useful for complex workflows or immersive content.
</p>
</Dialog>

Multi-step

Live preview

Step indicator + back/next footer — built with a single step state

App.tsx
tsx
const [multiStep, setMultiStep] = useState(false)
const [step, setStep] = useState(1)
 
<Dialog
open={multiStep}
onClose={() => setMultiStep(false)}
title={`Create project — Step ${step} of 3`}
subtitle={['Name your project', 'Configure settings', 'Invite team'][step - 1]}
size="sm"
showCloseButton
footer={
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<Button variant="outlined"
onClick={() => step === 1 ? setMultiStep(false) : setStep(s => s - 1)}>
{step === 1 ? 'Cancel' : '← Back'}
</Button>
<Button variant="filled"
onClick={() => step === 3 ? setMultiStep(false) : setStep(s => s + 1)}>
{step === 3 ? 'Create project' : 'Next →'}
</Button>
</div>
}
>
<div style={{ padding: '16px 24px' }}>
{/* Step indicator */}
<div style={{ display: 'flex', alignItems: 'center', gap: 0, marginBottom: 24 }}>
{[1, 2, 3].map((s, i) => (
<div key={s} style={{ display: 'flex', alignItems: 'center', flex: i < 2 ? 1 : 'none' }}>
<div style={{
width: 28, height: 28, borderRadius: '50%', flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 12, fontWeight: 600,
background: s < step ? 'var(--color-primary)' : s === step ? 'var(--color-primary-container)' : 'var(--color-surface-variant)',
color: s < step ? '#fff' : s === step ? 'var(--color-primary)' : 'var(--color-text-disabled)',
border: s === step ? '2px solid var(--color-primary)' : 'none',
}}>
{s < step ? '✓' : s}
</div>
{i < 2 && (
<div style={{ flex: 1, height: 2, background: s < step ? 'var(--color-primary)' : 'var(--color-border)' }} />
)}
</div>
))}
</div>
{step === 1 && <Input label="Project name" placeholder="My awesome project" />}
{step === 2 && <Input label="Description" placeholder="What is this project about?" />}
{step === 3 && <Input label="Invite teammates" placeholder="email@company.com" leadingIcon="mail" helperText="Separate multiple emails with commas" />}
</div>
</Dialog>

Command palette

Live preview

⌘K-style search overlay — built with Dialog + search input + command items

App.tsx
tsx
const [command, setCommand] = useState(false)
 
const commands = [
{ icon: 'ti-file', label: 'New file', shortcut: '⌘N', group: 'Create' },
{ icon: 'ti-folder', label: 'Open folder', shortcut: '⌘O', group: 'Create' },
{ icon: 'ti-search', label: 'Find in files', shortcut: '⌘⇧F', group: 'Search' },
{ icon: 'ti-settings', label: 'Settings', shortcut: '⌘,', group: 'Navigation' },
{ icon: 'ti-terminal', label: 'New terminal', shortcut: '⌘⇧`', group: 'Navigation' },
]
 
<Button variant="filled" onClick={() => setCommand(true)}>
Command palette ⌘K
</Button>
 
<Dialog
open={command}
onClose={() => setCommand(false)}
showCloseButton={false}
size="sm"
>
<div style={{ padding: '8px 0' }}>
{/* Search input */}
<div style={{ padding: '8px 16px 10px', borderBottom: '0.5px solid var(--color-border-tertiary)', display: 'flex', alignItems: 'center', gap: 8 }}>
<i className="ti ti-search" style={{ fontSize: 16, color: 'var(--color-text-secondary)', flexShrink: 0 }} aria-hidden="true" />
<input
autoFocus
placeholder="Search commands..."
style={{
flex: 1,
background: 'none',
border: 'none',
outline: 'none',
fontSize: 14,
color: 'var(--color-text-primary)',
padding: '4px 0',
}}
/>
<kbd style={{ fontSize: 11, color: 'var(--color-text-tertiary)', background: 'var(--color-background-secondary)', padding: '2px 6px', borderRadius: 4, border: '0.5px solid var(--color-border-tertiary)' }}>
ESC
</kbd>
</div>
{/* Command items */}
{commands.map(item => (
<div key={item.label} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 16px', cursor: 'pointer', fontSize: 13, color: 'var(--color-text-primary)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<i className={`ti ${item.icon}`} style={{ fontSize: 16, color: 'var(--color-text-secondary)' }} aria-hidden="true" />
{item.label}
</div>
<kbd style={{ fontSize: 11, color: 'var(--color-text-tertiary)', background: 'var(--color-background-secondary)', padding: '2px 6px', borderRadius: 4, border: '0.5px solid var(--color-border-tertiary)' }}>
{item.shortcut}
</kbd>
</div>
))}
</div>
</Dialog>

Props / API

Dialog is controlled through open and onClose; dismissal, position, size, intent, media, and footer composition are configurable.

Props

PropTypeDefaultDescription
openbooleanfalseWhether the dialog is visible
onClose() => void—Called when dialog should close (ESC, backdrop, close button)
type'default' | 'destructive' | 'success' | 'warning' | 'info''default'Affects icon and color scheme
position'center' | 'bottom' | 'left' | 'right''center'Where the dialog appears on screen
size'xs' | 'sm' | 'md' | 'lg' | 'fullscreen''md'Width of the dialog panel (fullscreen = entire viewport)
titlestring—Title shown in the dialog header
subtitlestring—Subtitle shown below the title
iconstring—Emoji or icon shown above the title
imageSrcstring—Hero image URL shown at the top of the dialog
imageAltstring—Alt text for the hero image
imageHeightnumber160Height of the hero image in px
showCloseButtonbooleantrueShows the × close button in the header
closeOnBackdropbooleantrueCloses dialog when clicking the backdrop
closeOnEscapebooleantrueCloses dialog on Escape key
footerReactNode—Footer content — usually action buttons
footerDividerbooleantrueShows a divider between body and footer
childrenReactNode—Dialog body content