Files
supabase/apps/design-system/content/docs/ui-patterns/forms.mdx
Danny White 7a77760a10 fix(studio): confirm before discarding dirty replication destination forms (#48522)
## What kind of change does this PR introduce?

Bug fix (dirty form dismissal for Replication destination sheets), plus
small docs/skill updates so agents pick up the existing modality
pattern.

## What is the current behavior?

Closing the Add/Edit destination sheet (Cancel, Escape, or backdrop)
discards in-progress form state with no confirm. Same for the nested
Create publication sheet.

## What is the new behavior?

Dirty closes go through `useConfirmOnClose` +
`DiscardChangesConfirmationDialog`, matching other Studio sheets.
Successful submit still closes without prompting.

Also: skills + `forms.mdx` now point at Modality “Dirty form dismissal”.

| After |
| --- |
| <img width="1024" height="759" alt="Replication Database Chisel
Toolshed Supabase"
src="https://github.com/user-attachments/assets/6f568a2a-c76b-442a-b592-d638bb36adc4"
/> |

### How to test

1. Studio → Database → Replication → **Add destination** (any pipelines
type with access).
2. Change a field so the form is dirty.
3. Try Cancel, Escape, and backdrop click → discard dialog appears;
**Keep editing** stays open; **Discard changes** closes.
4. Submit successfully with a valid config → sheet closes with no
discard dialog.
5. Repeat for **Edit destination** from a destination row menu.
6. Optional: Add destination → create a new publication from the
publication picker → dirty that nested sheet and dismiss the same way.
7. Optional: Add destination → Read Replica → change region → dismiss →
discard dialog; deploy still closes without prompting.

## Additional context

Sheet owns the close guard; forms report dirty via a ref because RHF
lives in the child. Nested `NewPublicationPanel` wires the guard
locally.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added unsaved-changes tracking to replication destination and
publication forms.
- Added confirmation prompts before closing forms with unsaved changes
via Cancel, Escape, or backdrop dismissal.
- Forms now reset appropriately after successful submission or confirmed
dismissal.

- **Documentation**
- Updated form and UI pattern guidance to document dirty-form dismissal
behavior for sheets and dialogs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-03 10:00:41 +10:00

65 lines
3.3 KiB
Plaintext

---
title: Forms
description: Common form patterns used in Studio settings pages and side panels.
---
Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels. This guide covers the most common form patterns and field types.
## Page Layout
Forms in page layouts typically use `PageSection` components with `Card` containers. Fields use `FormItemLayout` with `layout="flex-row-reverse"` for horizontal alignment.
<ComponentPreview
name="form-patterns-pagelayout"
description="Complete form example with all field types in a PageLayout pattern"
peekCode
wide
/>
## Side Panel
Forms in side panels (Sheets) use `FormItemLayout` with `layout="horizontal"` on wider panels and `layout="vertical"` on panels with a size of `sm` or below. The form is typically wrapped in a `Sheet` component.
<ComponentPreview
name="form-patterns-sidepanel"
description="Complete form example with all field types in a SidePanel/Sheet pattern"
peekCode
wide
/>
## Field Arrays
The form previews above include both repeated-field patterns used across Studio:
- **Field Array** for repeated single-value rows such as redirect URIs.
- **Key/Value Field Array** for repeated text pairs such as headers, parameters, and config entries.
Use the shared [Single Value Field Array](../fragments/single-value-field-array) fragment when each row is one text input managed by `react-hook-form`.
Use the shared [Key/Value Field Array](../fragments/key-value-field-array) fragment when each row is two text inputs managed by `react-hook-form`.
Keep repeated-row validation in the form schema or shared validation helper, not in the fragment component itself.
Build a custom row when the cells are mixed controls, such as an input paired with a `Select`.
## Best practices
1. **Always use FormItemLayout**: Use `FormItemLayout` instead of manually composing `FormItem`, `FormLabel`, `FormMessage`, and `FormDescription`.
2. **Layout selection**:
- Use `layout="flex-row-reverse"` for page layouts (horizontal alignment)
- Use `layout="horizontal"` for side panels with more width
- Use `layout="vertical"` for side panels with limited width
3. **Wrap inputs in FormControl*Shadcn***: Always wrap form inputs with `FormControl` to ensure proper form integration.
4. **Use Cards for grouping**: Wrap form sections in `Card` components with `CardContent` and `CardFooter` for actions.
5. **Handle dirty state**: Show cancel buttons and disable save buttons based on `form.formState.isDirty`. Make sure you destructure `isDirty` from `form.formState` (see https://react-hook-form.com/docs/useform/formstate). When the form is in a dialog or sheet, also follow [Dirty form dismissal](./modality#dirty-form-dismissal) so Cancel, Escape, and backdrop ask before discarding unsaved changes.
6. **Error handling**: Match feedback to its scope. Use `FormMessage` or `FieldError` for field validation. Show submission failures inline near the form actions when the user needs to retry or change something. Reserve toasts for non-blocking feedback or completed operations whose originating surface is no longer visible.
7. **Loading states**: Show loading states on submit buttons using the `loading` prop.
8. **Form IDs**: When submit buttons are outside the form, use a form ID and reference it with the `form` prop on the button.