Skip to content

Dialog

A modal for one short task, like renaming a server. A native <dialog> that opens without JavaScript.

OverlaysIsland

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.

Rename server

Agents see the new name the next time they list tools.

Example
<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.

Connection works

Linear answered in 182 ms and listed 14 tools.

Checked just now
Example
<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

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

Dialog props
PropTypeDefaultDescription
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.
descriptionRemixNode–One sentence under the title, used as the accessible description.
size'sm' | 'md' | 'lg''md'Width 400, 480 or 640px.
footerRemixNode–Buttons, right aligned. A non-button first child is a hint on the left.
actionstring–Where the form posts. Omit for a method="dialog" form that just closes.
method'dialog' | 'get' | 'post''post' with action, else 'dialog'Form method.
encTypestring–Form encoding, e.g. multipart/form-data for uploads.
openboolean–Server-rendered open (URL-driven). Upgraded to a modal after hydration.
closeHrefstring–URL restored after a URL-driven dialog closes.
hideCloseboolean–Hide the close (x) button in the header.
lightDismissbooleantrueClose on a backdrop click. Escape always closes.
documentbooleantrueA 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

DialogTrigger props
PropTypeDefaultDescription
target*string–id of the Dialog, Sheet or ConfirmDialog to open.
hrefstring–Render a link instead: loads the URL-driven dialog without JS, opens in place with JS.

Also accepts every Button prop.

DialogClose

DialogClose props
PropTypeDefaultDescription
childrenRemixNode'Cancel'Label.
variantButtonVariant'secondary'Same variants as Button.
valuestring'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.