Overview
FormFooter provides a consistent footer pattern for forms across all containers (PageCard, DittoSheet, PropertyPanel, Dialog). It features a 3-zone layout: left actions (Cancel, Reset), center status (messages, saving indicators), and right actions (primary submit).
Key Features
- Three-zone layout: left actions, center status, right actions
- formId pattern for connecting to forms outside the footer
- Built-in status messages (info, success, warning, error)
- Consistent styling across all containers
Examples
Basic Usage
Simple cancel/save footer with formId connection to FormBlock.
With Status Message
Center zone displays status messages for validation, saving state, or unsaved changes.
Center zone displays status messages for validation, saving state, or unsaved changes.
In DittoSheet
FormFooter works seamlessly with DittoSheet using the formId pattern.
Multiple Actions
Both left and right zones support multiple buttons.
Both left and right zones support multiple buttons.
Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
formId | string | - | Form ID to connect submit button via HTML form attribute |
submitLabel | string | "Save" | Submit button label |
cancelLabel | string | - | Cancel button label (shown if onCancel provided) |
onCancel | () => void | - | Cancel button handler |
onSubmit | () => void | - | Submit handler (only when formId not provided) |
isSubmitting | boolean | false | Whether form is currently submitting |
disabled | boolean | false | Disable all buttons |
leftActions | ReactNode | - | Custom left zone content (replaces cancel button) |
rightActions | ReactNode | - | Custom right zone content (replaces submit button) |
status | { type, message } | - | Status message object with type and message |
centerContent | ReactNode | - | Custom center zone content (replaces status) |
sticky | boolean | false | Whether to use sticky positioning |
Usage
import { FormFooter, DittoSheet } from '@/ui/components';
import { FormBlock, FormTextField } from '@/ui/blocks';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Name is required'),
});
function MySheetForm() {
const [open, setOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (data) => {
setIsSubmitting(true);
try {
await saveData(data);
setOpen(false);
toast.success('Saved successfully');
} finally {
setIsSubmitting(false);
}
};
return (
<DittoSheet
open={open}
onOpenChange={setOpen}
title="Edit Settings"
footer={
<FormFooter
formId="settings-form"
cancelLabel="Cancel"
onCancel={() => setOpen(false)}
submitLabel="Save"
isSubmitting={isSubmitting}
/>
}
>
<FormBlock
id="settings-form"
schema={schema}
onSubmit={handleSubmit}
hideActions
>
<FormTextField name="name" label="Name" />
</FormBlock>
</DittoSheet>
);
}