Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

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

PropTypeDefaultDescription
triggerReactNodeThe control that opens it, such as a Button. Leave it out to open it from code with open.
openbooleanWhether it's open. Pair with onOpenChange to control it.
defaultOpenbooleanfalseWhether it starts open when uncontrolled. Defaults to false.
onOpenChange((open: boolean) => void)Called when it opens or closes.
title

required

stringIts title, over a divider, which names it.
descriptionReactNodeCopy under the divider that says what the form is for and can hold links; it describes the dialog.
childrenReactNodeThe fields, such as a TextField. The first one takes focus once the dialog has settled.
validbooleantrueWhether the form can be sent, which enables Submit. Defaults to true.
onSubmit

required

() => 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.
submitLabelstring"Save"Submit's label. Defaults to "Save".
cancelLabelstring"Cancel"Cancel's label. Defaults to "Cancel".
size"sm" | "md""sm""sm" (400px, the default) or "md" (560px).
closeLabelstring"Close"Names the close button. Defaults to "Close".
classNamestringClasses for the dialog's content, the element that carries data-bt="FormDialog".
styleCSSPropertiesInline styles for the same element.
refRef<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.

KeysWhat they do
EnterSubmits the form from a field while it's valid
EscapeCloses it and puts focus back on what opened it, unless a submit is pending
Tab or Shift+TabMoves 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.

See it in the roastery demo's settings. See it in the Deckhand demo.