CentercodeDitto
DittoPorygonAPI

FormBlock

Build consistent forms with validation, auto-save, and wizard modes

Overview

FormBlock provides a consistent form-building system that prevents drift in field spacing, validation feedback, layouts, and action button placement. Built on react-hook-form with Zod validation.

Key Features

Form Modes

  • Submit mode - traditional form with submit button
  • Auto-save mode - saves as user makes changes
  • Wizard mode - multi-step form with navigation

Validation

  • Zod schema-based validation
  • Hybrid validation (submit, then onChange)
  • Per-step validation in wizard mode

Field Components

  • 15 built-in field types
  • Conditional field visibility
  • Custom field escape hatch

Layout & Structure

  • Responsive 1-4 column grids
  • Collapsible sections
  • Consistent spacing and styling

When to Use FormBlock

  • Admin interfaces and settings pages
  • User profiles and configuration forms
  • Multi-step wizards and onboarding flows
  • Any form where consistency is important

Live Examples

Basic Submit Form

Traditional form with text inputs, select, and textarea. Validates on submit, then revalidates on change.

User Information

Auto-Save Settings

Settings form that saves automatically as you make changes. Shows 'Saving...' and 'Saved' status.

Display

Notifications

Email Notifications

Receive updates via email

Push Notifications

Receive push notifications in your browser

Playback

Changes are saved automatically 1.5 seconds after you stop typing. The status indicator shows save progress.

Conditional Fields

Fields that show/hide based on other field values using the 'when' prop.

Contact Preferences

Select a contact method to see additional fields appear. The 'when' prop supports 'is', 'in', and 'isNot' conditions.

FormGroup

Visual container for grouping related form fields. Commonly used for toggle clusters or related settings.

Project


Visibility & Behavior

Control how this project appears to participants

Pin to Menu

Show in the sidebar navigation

Allow Updates

Let users edit after submission

Mandatory

Require completion for all participants


Developer Options

Advanced settings for debugging

Show Advanced Options

Enable developer-only settings

Toggle "Show Advanced Options" to see a conditional FormGroup with outlined variant. FormGroup supports muted (default), outlined, and plain variants.

// FormGroup provides a visual container for grouped fields
<FormSection title="Settings" description="Configure options" divider="top">
  <FormGroup>
    <FormSwitchField name="pinToMenu" label="Pin to Menu" />
    <FormSwitchField name="allowUpdates" label="Allow Updates" />
    <FormSwitchField name="mandatory" label="Mandatory" />
  </FormGroup>
</FormSection>

// Variants: 'muted' (default), 'outlined', 'plain'
<FormGroup variant="outlined">...</FormGroup>

// Spacing: 'sm', 'md' (default), 'lg'
<FormGroup spacing="lg">...</FormGroup>

// Conditional visibility
<FormGroup when={{ field: 'showAdvanced', is: true }}>
  <FormSwitchField name="advanced1" label="Advanced 1" />
</FormGroup>

Auto-Title Formatting

Text fields with autoTitle automatically convert input to Title Case on blur. Users can toggle this off via the magic wand button.

Resource Details

Try typing "the quick brown fox" and tabbing out. The magic wand icon (right side) indicates auto-formatting is enabled. Click it to disable for this field.

// Auto-title converts text to Title Case on blur
<FormTextField
  name="resourceTitle"
  label="Resource Title"
  autoTitle  // Enabled by default, user can toggle off
  placeholder="Enter a title..."
/>

// Or use the title helper (autoTitle enabled by default)
formHelpers.title('resourceTitle', 'Resource Title')

Card Picker Field

Visual card-based selection with icons, search, and category filtering. Supports single and multi-select modes.

Resource Configuration

Survey

Collect structured feedback with questions and ratings

Document

Share documentation, guides, or reference materials

Video

Embed video content for training or demonstrations

Poll

Quick single-question polls for fast insights

Discussion

Open forum for participant conversations

Bug Report

Structured issue reporting with severity levels

Idea Submission

Collect feature ideas and suggestions

Announcement

Important updates and notifications

Require Prerequisites

Require users to complete other resources first

Toggle "Require Prerequisites" to reveal a multi-select card picker for choosing prerequisite resources.

// Single select card picker
<FormCardPickerField
  name="resourceType"
  label="Resource Type"
  options={resourceTypeOptions}
  categories={resourceCategories}
  defaultViewMode="compact"
/>

// Multi-select with when condition
<FormCardPickerField
  name="requiredResources"
  label="Required Resources"
  options={resourceOptions}
  multiple
  when={{ field: 'isConditional', is: true }}
/>

// Using formHelpers
formHelpers.cardPicker('resourceType', 'Resource Type', resourceTypeOptions, {
  categories: resourceCategories,
  multiple: false,
})

Visual Identity Field

Unified visual identity editor for icon, color, card image, banner image, and templates. Supports direct manipulation for image positioning.

Project Branding

Click on the card or banner previews to position images by dragging. Use scroll wheel to zoom. The component automatically manages icon selection, color, image uploads, and text templates.

// Visual identity editor (label defaults to "Visual Identity")
<FormPrettyDesignerField
  name="design"
  targetType="project"
  targetName={projectName}
  targetDescription={projectDescription}
  programId={programId}
/>

// Using formHelpers
formHelpers.prettyDesigner('design', 'Visual Identity', 'project', projectName, {
  targetDescription: projectDescription,
  programId: programId,
})

Form in Dialog

Use inDialog prop for forms inside modals. Actions stick to the bottom while content scrolls.

When inDialog is set, the form actions stick to the bottom of the dialog with a border, and the content area scrolls. Try adding more content to see the scroll behavior.

// FormBlock optimized for use inside a Dialog
<Dialog>
  <DialogTrigger asChild>
    <Button>Edit Resource</Button>
  </DialogTrigger>
  <DialogContent className="max-h-[80vh] overflow-hidden flex flex-col">
    <DialogHeader>
      <DialogTitle>Edit Resource</DialogTitle>
    </DialogHeader>
    <FormBlock
      schema={schema}
      defaultValues={defaultValues}
      onSubmit={handleSubmit}
      onCancel={() => setOpen(false)}
      showCancel
      inDialog  // Enables sticky actions and proper scroll
    >
      {/* Form content scrolls, actions stick to bottom */}
      <FormSection title="Details">
        <FormTextField name="title" label="Title" />
        <FormTextareaField name="description" label="Description" rows={4} />
      </FormSection>
    </FormBlock>
  </DialogContent>
</Dialog>

Dialog Wizard

Multi-step form for use inside dialogs. For full-page multi-step processes, use LayoutWizard instead.

Important: FormBlock wizard mode is designed for compact, dialog-based multi-step forms. For full-page wizard experiences with sidebar navigation, use LayoutWizard instead. See the LayoutWizard demo for an example.

The wizard shows step progress at the top. Each step validates before advancing. The inDialog prop enables proper scroll behavior and sticky action buttons.

Declarative Sections

Alternative API using a sections array instead of JSX children. Useful for dynamic form generation.

User Information

const sections = [
  {
    id: 'basic',
    title: 'Basic Information',
    columns: 2,
    fields: [
      formHelpers.text('name', 'Name', { placeholder: 'Enter name...' }),
      formHelpers.email('email', 'Email'),
      formHelpers.select('role', 'Role', roleOptions),
      formHelpers.switch('isActive', 'Active'),
    ],
  },
];

<FormBlock schema={schema} sections={sections} onSubmit={handleSubmit} />

Field Helpers

Utility functions for creating common field configurations with sensible defaults.

HelperField TypeComponent
formHelpers.text()Text inputFormTextField
formHelpers.title()Title (auto-format)FormTextField + autoTitle
formHelpers.email()Email inputFormTextField
formHelpers.password()Password inputFormTextField
formHelpers.textarea()Multi-line textFormTextareaField
formHelpers.select()Single selectFormSelectField
formHelpers.multiSelect()Multi-selectFormMultiSelectField
formHelpers.switch()Card switch (with icon)FormCardSwitchField
formHelpers.toggle()Inline toggleFormSwitchField
formHelpers.checkbox()CheckboxFormCheckboxField
formHelpers.radio()Radio groupFormRadioField
formHelpers.date()Date pickerFormDateField
formHelpers.dateRange()Date rangeFormDateRangeField
formHelpers.color()Color pickerFormColorField
formHelpers.richText()Rich textFormRichTextField
formHelpers.tag()Tag inputFormTagField
formHelpers.cardPicker()Card pickerFormCardPickerField
formHelpers.prettyDesigner()Visual identityFormPrettyDesignerField
formHelpers.custom()Custom renderFormCustomField
const fields = [
  formHelpers.text('name', 'Name', { placeholder: 'Enter name...' }),
  formHelpers.title('projectTitle', 'Project Title'),  // Auto-title-case enabled
  formHelpers.email('email', 'Email'),
  formHelpers.password('password', 'Password'),
  formHelpers.textarea('bio', 'Bio', { rows: 4 }),
  formHelpers.select('country', 'Country', countryOptions),
  formHelpers.multiSelect('tags', 'Tags', tagOptions),
  formHelpers.switch('notifications', 'Enable Notifications'),
  formHelpers.checkbox('terms', 'I agree to the terms'),
  formHelpers.radio('priority', 'Priority', priorityOptions),
  formHelpers.date('startDate', 'Start Date'),
  formHelpers.dateRange('availability', 'Availability'),
  formHelpers.color('brandColor', 'Brand Color'),
  formHelpers.richText('content', 'Content'),
  formHelpers.tag('keywords', 'Keywords'),
];

Props Reference

Core Props

Essential props for form configuration and data handling.

PropTypeDefaultDescription
schemaz.ZodTyperequiredZod schema for validation
mode'submit' | 'auto-save' | 'wizard''submit'Form mode
defaultValuesPartial<T>-Initial form values
sectionsFormSectionDefinition[]-Declarative sections
childrenReactNode-JSX children
inDialogbooleanfalseOptimize layout for dialog/modal use

Event Handlers

Callbacks for form submission, auto-save, and navigation events.

PropTypeDefaultDescription
onSubmit(values: T) => void-Called on form submission
onAutoSave(values, field) => void-Called on auto-save (auto-save mode)
onStepChange(step, direction) => void-Called when wizard step changes
onCancel() => void-Called when cancel is clicked
onError(errors) => void-Called on validation error

Wizard Options

Props for controlling wizard mode behavior and navigation.

PropTypeDefaultDescription
currentStepnumber-Controlled current step
showStepIndicatorbooleantrueShow step progress indicator
allowStepJumpbooleanfalseAllow clicking on step indicator to jump
validateOnStepChangebooleantrueValidate before advancing steps

Quick Start

import { FormBlock, FormSection, FormGrid, FormTextField, formHelpers } from '@/ui/blocks';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email'),
});

<FormBlock
  schema={schema}
  defaultValues={{ name: '', email: '' }}
  onSubmit={async (values) => {
    await saveData(values);
    toast.success('Saved!');
  }}
>
  <FormSection title="Contact Info">
    <FormGrid columns={2}>
      <FormTextField name="name" label="Name" placeholder="Enter name..." />
      <FormTextField name="email" label="Email" inputType="email" />
    </FormGrid>
  </FormSection>
</FormBlock>