Dialog
A modal dialog for confirmations and short forms. It renders above the page, keeps keyboard focus inside, and returns focus to the trigger when it closes.
import { Dialog } from 'jarvis-react-ui';
Confirmation
The dialog is controlled: keep open in state and update it in onOpenChange. Put the buttons in
actions; they appear in the footer, right-aligned.
import { useState } from 'react';
import { Button, Dialog, useToast } from 'jarvis-react-ui';
export default function Confirm() {
const [open, setOpen] = useState(false);
const { toast } = useToast();
function purge() {
setOpen(false);
toast({ variant: 'success', title: 'Cache purged' });
}
return (
<>
<Button variant="danger" onClick={() => setOpen(true)}>
Purge cache
</Button>
<Dialog
open={open}
onOpenChange={setOpen}
title="Purge cache"
description="All cached telemetry will be deleted."
size="sm"
actions={
<>
<Button variant="ghost" size="sm" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button variant="danger" size="sm" onClick={purge}>
Purge
</Button>
</>
}
>
This cannot be undone. Running sessions keep their data.
</Dialog>
</>
);
}
Form
initialFocusRef chooses the element that gets focus when the dialog opens, here the input. Without
it, the first focusable element (usually the close button) gets focus.
import { useRef, useState } from 'react';
import type { FormEvent } from 'react';
import { Button, Dialog, Input, Label } from 'jarvis-react-ui';
export default function Form() {
const [open, setOpen] = useState(false);
const [callsign, setCallsign] = useState('JARVIS');
const inputRef = useRef<HTMLInputElement>(null);
function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setOpen(false);
}
return (
<>
<Button onClick={() => setOpen(true)}>Rename: {callsign}</Button>
<Dialog
open={open}
onOpenChange={setOpen}
title="Rename assistant"
initialFocusRef={inputRef}
>
<form id="rename-form" onSubmit={submit} className="flex flex-col gap-2">
<Label htmlFor="callsign">Callsign</Label>
<Input
id="callsign"
ref={inputRef}
fullWidth
value={callsign}
onChange={(e) => setCallsign(e.target.value)}
/>
<div className="flex justify-end gap-2 pt-2">
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setOpen(false)}
>
Cancel
</Button>
<Button type="submit" variant="primary" size="sm">
Save
</Button>
</div>
</form>
</Dialog>
</>
);
}
Sizes
sm, md (default) and lg cap the width at 360, 520 and 720px. Long content scrolls inside the
body while the header and footer stay visible.
import { useState } from 'react';
import { Button, Dialog } from 'jarvis-react-ui';
import type { DialogSize } from 'jarvis-react-ui';
const SIZES: DialogSize[] = ['sm', 'md', 'lg'];
export default function Sizes() {
const [size, setSize] = useState<DialogSize | null>(null);
return (
<>
{SIZES.map((s) => (
<Button key={s} onClick={() => setSize(s)}>
{s}
</Button>
))}
<Dialog
open={size !== null}
onOpenChange={() => setSize(null)}
title={`Size ${size ?? ''}`}
size={size ?? 'md'}
>
The panel is at most {size === 'sm' ? 360 : size === 'lg' ? 720 : 520}px wide and
never wider than the viewport.
</Dialog>
</>
);
}
Closing
The dialog calls onOpenChange(false) when the user presses Escape, clicks the backdrop or uses
the close button. Set closeOnBackdrop={false} for dialogs that must be answered explicitly, and
showClose={false} to hide the close button.
Accessibility
role="dialog"witharia-modal="true", named bytitle(aria-labelledby) and described bydescription(aria-describedby). UsehideTitleto keep the name while hiding the heading.- Focus moves into the dialog on open, Tab and Shift+Tab cycle inside it, and focus returns to the previously focused element on close.
- Page scrolling is locked while the dialog is open.
- The dialog is portaled to
document.body, so ancestors withoverflow: hiddenortransformcannot clip it.
Sound
Opening plays menu_open, closing plays menu_close.
API
| Prop | Type | Default | Description |
|---|---|---|---|
onOpenChange (required) |
(open: boolean) => void |
Called with false when the user closes the dialog (Escape, backdrop, close button). |
|
open (required) |
boolean |
Whether the dialog is shown. The dialog is controlled. | |
title (required) |
ReactNode |
Heading of the dialog; also its accessible name. | |
actions |
ReactNode |
Footer content, typically buttons, aligned to the right. | |
children |
ReactNode |
Dialog body. | |
className |
string |
Additional class names for the dialog panel. | |
closeOnBackdrop |
boolean |
true |
Closes the dialog when the backdrop is clicked. |
description |
ReactNode |
Optional text below the title; referenced by aria-describedby. |
|
hideTitle |
boolean |
false |
Hides the title visually while keeping it as the accessible name. |
initialFocusRef |
RefObject<HTMLElement | null> |
Element that receives focus when the dialog opens. Defaults to the first focusable element. | |
showClose |
boolean |
true |
Shows the close button in the header. |
size |
'sm' | 'md' | 'lg' |
'md' |
Maximum width: sm 360px, md 520px, lg 720px. |