CentercodeDitto
DittoPorygonAPI

FormFooter

Unified footer for forms with 3-zone layout

Overview

FormFooter provides a consistent footer pattern for forms across all containers (PageCard, 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.

You have unsaved changes

In PropertyPanel Overlay

FormFooter works seamlessly with PropertyPanel overlay mode using the formId pattern.

Multiple Actions

Both left and right zones support multiple buttons.

Both left and right zones support multiple buttons.

Fixed to Viewport

Viewport-fixed footer for standalone forms outside containers. Opens in an overlay to demonstrate.

Use the fixed prop for forms that need a permanently visible footer locked to the bottom of the viewport. This is ideal for wizard preview steps, full-page forms, or any context where the footer should always be accessible regardless of scroll position.

Floating Footer

Sticky footer with floating card appearance within scroll container.

Use the floating prop for footers that should stick within a scroll container but have a floating card appearance with rounded corners, shadow, and margins. This is ideal for forms in LayoutShell where the footer should feel like a floating action bar.

Scroll down to see the floating footer behavior. The footer stays sticky at the bottom of this scroll container with a floating card appearance.

Sample content block 1 - scroll to see floating footer

Sample content block 2 - scroll to see floating footer

Sample content block 3 - scroll to see floating footer

Sample content block 4 - scroll to see floating footer

Sample content block 5 - scroll to see floating footer

Sample content block 6 - scroll to see floating footer

Props Reference

PropTypeDefaultDescription
formIdstring-Form ID to connect submit button via HTML form attribute
submitLabelstring"Save"Submit button label
cancelLabelstring-Cancel button label (shown if onCancel provided)
onCancel() => void-Cancel button handler
onSubmit() => void-Submit handler (only when formId not provided)
isSubmittingbooleanfalseWhether form is currently submitting
disabledbooleanfalseDisable all buttons
leftActionsReactNode-Custom left zone content (replaces cancel button)
rightActionsReactNode-Custom right zone content (replaces submit button)
status{ type, message }-Status message object with type and message
centerContentReactNode-Custom center zone content (replaces status)
stickybooleanfalseSticky to bottom of scroll container
floatingbooleanfalseFloating card appearance, sticky within scroll container
fixedbooleanfalseFixed to bottom of viewport (needs pb-20 on content)
maxWidthstring"max-w-4xl"Max width class for content alignment (only with fixed)
fullWidthbooleanfalseButtons fill available width evenly

Usage

import { FormFooter, PropertyPanel, PropertyPanelContent } 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 (
    <PropertyPanel
      overlay
      open={open}
      onOpenChange={setOpen}
      title="Edit Settings"
      footer={
        <FormFooter
          formId="settings-form"
          cancelLabel="Cancel"
          onCancel={() => setOpen(false)}
          submitLabel="Save"
          isSubmitting={isSubmitting}
        />
      }
    >
      <PropertyPanelContent>
        <FormBlock
          id="settings-form"
          schema={schema}
          onSubmit={handleSubmit}
          hideActions
        >
          <FormTextField name="name" label="Name" />
        </FormBlock>
      </PropertyPanelContent>
    </PropertyPanel>
  );
}