FormDialog
A dialog that asks for a value or two, Submit waiting until the form is valid.
import { FormDialog } from "@butteryui/react";Demo
Opens 400px wide and saves with Add email
When to use it
Use it for
- Adding or changing one or two values from a settings page, such as a backup email
- Naming something, such as a saved view
Not for
- A longer form: give it its own page or a SettingsSection
- Choosing between a few ways to do something: use ChoiceDialog or a RadioCardGroup in a dialog
- Confirming a purchase or a change with consequences: use ConsequenceDialog
Examples
Backup email
A form dialog whose Submit waits for an email address, then saves it.
No backup email
Name the copy
A dialog opened from code with open: Duplicate makes the copy, then asks what to call it.
Café price list
Rename saved view
Renaming a saved view, the save taking a moment: Submit waits for a new name, then shows busy until it's done.
View: Unpaid
Sign in settings
A settings section whose row changes its value through a form dialog, the row's state updating once it's saved.
Signing in
How you get back into the roastery's account.
Backup email
None yet. We'd send sign-in codes here.
Two-step sign-in
On, with a code from an authenticator app.
Sizes
The two widths: "sm" (400px, the default) for a value or two, and "md" (560px) for fields side by side.
Order email: orders@halfmoon.example
Ships to 12 Harbor Street, Portside
Variants and states
A backup email
Focus lands on the field; Add email waits for an address.
Props
FormDialog
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactNode | The control that opens it, such as a Button. Leave it out to open it from code with open. | |
open | boolean | Whether it's open. Pair with onOpenChange to control it. | |
defaultOpen | boolean | false | Whether it starts open when uncontrolled. Defaults to false. |
onOpenChange | ((open: boolean) => void) | Called when it opens or closes. | |
titlerequired | string | Its title, over a divider, which names it. | |
description | ReactNode | Copy under the divider that says what the form is for and can hold links; it describes the dialog. | |
children | ReactNode | The fields, such as a TextField. The first one takes focus once the dialog has settled. | |
valid | boolean | true | Whether the form can be sent, which enables Submit. Defaults to true. |
onSubmitrequired | () => void | Promise<void> | Sends the form: called on Submit, or Enter in a field, while it's valid. It closes once this returns, or once the promise it returns resolves, Submit showing busy until then and nothing closing it meanwhile; a rejected promise leaves it open. | |
submitLabel | string | "Save" | Submit's label. Defaults to "Save". |
cancelLabel | string | "Cancel" | Cancel's label. Defaults to "Cancel". |
size | "sm" | "md" | "sm" | "sm" (400px, the default) or "md" (560px). |
closeLabel | string | "Close" | Names the close button. Defaults to "Close". |
className | string | Classes for the dialog's content, the element that carries data-bt="FormDialog". | |
style | CSSProperties | Inline styles for the same element. | |
ref | Ref<HTMLDivElement> | The dialog element. |
Accessibility
A modal dialog named by its title and described by its copy. Focus goes to its first field once it has settled and comes back to what opened it. Its content is a form: Enter in a field submits while it's valid, and Submit stays focusable but disabled (aria-disabled) until then, busy (aria-busy) while a returned promise is pending.
| Keys | What they do |
|---|---|
| Enter | Submits the form from a field while it's valid |
| Escape | Closes it and puts focus back on what opened it, unless a submit is pending |
| Tab or Shift+Tab | Moves through the copy's links, the fields, Cancel, Submit and the close button, staying inside the dialog |
Motion
It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.
With reduced motion: It fades in and out with its scrim in 100ms, without growing from 97% or rising.
Layout
It's 400px wide (--bt-w-dialog) by default or 560px with size="md" (--bt-w-dialog-md), never wider than the window less 16px a side, and as tall as its content until it meets the window's edges, when its content scrolls inside. Its title sits over a 1px divider, the copy and fields wrap, and Cancel and Submit, 36px tall, sit at the end of its footer.
Related
See it in the roastery demo's settings. See it in the Deckhand demo.