shadcn.io is not affiliated with official shadcn/ui
Modal Create Form Block for React
A project manager's Projects page with its New project dialog shown open: name, description and category, and a discard guard that swaps the footer for Keep editing and Discard when a dirty form is dismissed.
Installation
Overview
Create a record without losing a half-written one, with this modal create form for React with Base UI. It opens from the Projects page of Kiteboard, a project manager: the page's header holds an outline New project Button, and the page itself is a drawn table under a soft scrim inside a hairline frame with a plus mark on each corner. The dialog holds a name (Input, 3 to 60 characters, not one that exists), an optional description (Textarea, 140 characters or fewer) and a category, each in a Field with a reserved line for its hint or problem. Escape, the scrim, the close button or Cancel on a clean form close it at once. On a dirty form the footer swaps, in the same space, for "Discard this project?" with Keep editing, where focus lands, and Discard; a second Escape means keep editing. Create project, the only Shine Button, checks the fields, adds a row to the table behind, closes and says so. For preview the dialog is drawn open in place when the page loads; New project opens the real Dialog, with the same fields and state. For projects, workspaces, labels and any create form in a modal.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the page: a native dialog element that is not modal, so nothing is trapped, hidden or focused. The New project button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same ProjectFields component and share the block's state, so what you typed in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
How does the discard guard work?
Every way out except Create goes through one function, ask. On a clean form it closes. On a dirty one (anything typed or chosen) it keeps the dialog open and swaps the footer for the question, which sits in the same grid cell as the footer so the dialog never changes height; focus goes to Keep editing and a polite message says what happened. Keep editing, or Escape again, goes back to the fields; Discard closes and clears the form. The dialog is controlled, so Escape and the scrim reach ask through the Dialog's own open handler.
What does Create project do, and how do I wire it up?
It checks the name (3 to 60 characters, and not the name of a project in the list), the description length and that a category is chosen. A problem shows under its field and focus goes to the first one; otherwise a row is added at the top of the drawn table, the dialog closes, focus returns to New project and a polite message says "Created Spring launch in Product. Nothing was saved in this demo." In your app post the values from create, wait for the server, add the row from the response and show a server refusal (a name that is taken) in the field's slot.
Is it accessible from the keyboard and to screen readers?
Yes. The real dialog is named by its title and described by its sentence, focus starts in the name field and stays inside until it closes, and every close returns focus to New project. The trigger is passed to DialogTrigger through render, and the dialog is controlled with open and onOpenChange. Because a modal hides the page behind it, a second polite status inside the dialog speaks while it is open.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet: the fields scroll above, and the footer stays pinned with its two buttons side by side. The real dialog keeps the Dialog's own phone layout, stuck to the bottom of the screen. The discard question uses the same footer space, with the question above the two buttons.
How do I connect it to react-hook-form or TanStack Form?
Keep the three values in the form and read its dirty state for the guard. With react-hook-form, formState.isDirty replaces isDirty here, handleSubmit(create) replaces the form's onSubmit and reset() replaces reset. With TanStack Form, use form.state.isDirty, validate each field on blur and call form.reset() from Discard.
Related
- Pro
Invite Members Dialog
Share and inviteA household calendar's Members page with its invite dialog shown open: one email field that knows the household's domain, so an outside address joins as a guest, the people who already have access, and a Send invite button that adds a pending row.
- Pro
Add Card Dialog
Payments and plansA transit wallet's payment methods page with its add-card dialog shown open: a card drawn in greys that fills in as you type, a number that groups itself and names its network, checks on the number, expiry and code, and an Add card button that keeps only the last four digits.
- Pro
Settings Drawer Form
Dialogs and layoutsA longer settings form in a bottom drawer with a fixed header and footer, soft shadows at the scrolling edges, a count of unsaved changes worked out from the fields and a guard against closing a dirty form.
- Pro
Text Input with Address Preview Form
Text fieldsA form panel with one required project-name field that shows the web address it will get as you type, flags a name that is taken or past 50 characters, and offers the next free address.
- Pro
Confirm Delete Dialog
Destructive confirmsA planner's project list with its delete confirmation shown open: an alert dialog that lists what goes with the project, asks for its name to be typed exactly, and keeps Delete off until it matches.
- Pro
Review and Submit Form
Dialogs and layoutsA review step: three sections of typed answers that each edit in place in the same boxes, a side cell of what changed, a Submit that names the section to finish, and a lock after a local submit.