Dialog
A modal for one short task, like renaming a server. A native <dialog> that opens without JavaScript.
Examples
Form dialog
The trigger is a native Invoker Command button. The content is one form: Cancel and the close button never submit or validate it.
<DialogTrigger target="rename-server" variant="secondary" icon="pencil">
Rename server
</DialogTrigger>
<Dialog
id="rename-server"
title="Rename server"
description="Agents see the new name the next time they list tools."
footer={<>
<DialogClose />
<Button type="submit">Rename server</Button>
</>}
>
<Field id="rename-server-name" label="Name">
<Input name="name" value="GitHub" required />
</Field>
</Dialog>Footer hint and small size
A non-button first child in the footer becomes a hint on the left.
<DialogTrigger target="test-connection" variant="secondary" icon="plug-zap">
Test connection
</DialogTrigger>
<Dialog
id="test-connection"
size="sm"
title="Connection works"
description="Linear answered in 182 ms and listed 14 tools."
footer={<>
<span>Checked just now</span>
<DialogClose variant="primary">Done</DialogClose>
</>}
/>Usage
import { Dialog, DialogTrigger, DialogClose } from '@switchboard/ui'
// URL-driven: works without JS, opens in place with JS.
<DialogTrigger target="rename-server" href="?dialog=rename-server">Rename server</DialogTrigger>
<Dialog
id="rename-server"
title="Rename server"
open={url.searchParams.get('dialog') === 'rename-server'}
closeHref={routes.servers.show.href({ id })}
action={routes.servers.rename.href({ id })}
method="post"
footer={<><DialogClose /><Button type="submit">Rename server</Button></>}
>
…
</Dialog>Props
Every component also takes mix, for css() and mixins applied after its own style.
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
| id* | string | – | Unique on the page. Triggers point at it; title and description ids derive from it. |
| title* | RemixNode | – | Heading, used as the accessible name. |
| description | RemixNode | – | One sentence under the title, used as the accessible description. |
| size | 'sm' | 'md' | 'lg' | 'md' | Width 400, 480 or 640px. |
| footer | RemixNode | – | Buttons, right aligned. A non-button first child is a hint on the left. |
| action | string | – | Where the form posts. Omit for a method="dialog" form that just closes. |
| method | 'dialog' | 'get' | 'post' | 'post' with action, else 'dialog' | Form method. |
| encType | string | – | Form encoding, e.g. multipart/form-data for uploads. |
| open | boolean | – | Server-rendered open (URL-driven). Upgraded to a modal after hydration. |
| closeHref | string | – | URL restored after a URL-driven dialog closes. |
| hideClose | boolean | – | Hide the close (x) button in the header. |
| lightDismiss | boolean | true | Close on a backdrop click. Escape always closes. |
| document | boolean | true | A posting form does a full document navigation. false: frame navigation, and the dialog follows the server `open` after each reload and focuses the first invalid field. Keep true on Tokens create. |
Also accepts every <dialog> attribute.
DialogTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
| target* | string | – | id of the Dialog, Sheet or ConfirmDialog to open. |
| href | string | – | Render a link instead: loads the URL-driven dialog without JS, opens in place with JS. |
Also accepts every Button prop.
DialogClose
| Prop | Type | Default | Description |
|---|---|---|---|
| children | RemixNode | 'Cancel' | Label. |
| variant | ButtonVariant | 'secondary' | Same variants as Button. |
| value | string | 'cancel' | Becomes the dialog returnValue. |
Also accepts every Button prop except href. Must be inside a Dialog.
Accessibility
- Opens with showModal(): the rest of the page is inert, Escape closes, and focus returns to the trigger.
- The dialog is named by its title (aria-labelledby) and described by its description.
- Initial focus lands on the first field; the close (x) button is last in DOM order. Put autofocus on another control to change it.
- Page scroll is locked while it is open.
Guidance
Do
- Title the dialog with the action and repeat it on the submit button: "Rename server".
- Keep it to one short task with a few fields.
- Use a URL-driven dialog when a link to it should work without JavaScript.
Don’t
- Nest a <form> inside a Dialog; its content is already one form.
- Open a dialog from another dialog.
- Use a dialog for long forms; use a Sheet or a page.