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.
Auto-Save Settings
Settings form that saves automatically as you make changes. Shows 'Saving...' and 'Saved' status.
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.
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.
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.
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)
fieldHelpers.title('resourceTitle', 'Resource Title')Card Picker Field
Visual card-based selection with icons, search, and category filtering. Supports single and multi-select modes.
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 fieldHelpers
fieldHelpers.cardPicker('resourceType', 'Resource Type', resourceTypeOptions, {
categories: resourceCategories,
multiple: false,
})Audience Picker Field
Domain-specific access level selector with optional segmentation by participant tags.
Select "Participant" to see the segmentation panel. Enable "Segment by Tags" to filter by participant tags.
// Basic audience picker
<FormAudiencePickerField
name="audience"
defaultLevel="participant"
/>
// With segmentation
<FormAudiencePickerField
name="audience"
tagsFieldName="audienceTags"
enableSegmentation
availableTags={participantTags}
/>
// Using fieldHelpers
fieldHelpers.audience('audience', 'Audience', {
enableSegmentation: true,
tagsFieldName: 'audienceTags',
availableTags: participantTags,
})Visual Identity Field
Unified visual identity editor for icon, color, card image, banner image, and templates. Supports direct manipulation for image positioning.
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 fieldHelpers
fieldHelpers.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 Resource Properties 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.
const sections = [
{
id: 'basic',
title: 'Basic Information',
columns: 2,
fields: [
fieldHelpers.text('name', 'Name', { placeholder: 'Enter name...' }),
fieldHelpers.email('email', 'Email'),
fieldHelpers.select('role', 'Role', roleOptions),
fieldHelpers.switch('isActive', 'Active'),
],
},
];
<FormBlock schema={schema} sections={sections} onSubmit={handleSubmit} />Field Helpers
Helper functions that reduce boilerplate for common field types. Similar to columnHelpers for AdminListTableBlock.
| Helper | Field Type | Component |
|---|---|---|
fieldHelpers.text() | Text input | FormTextField |
fieldHelpers.title() | Title (auto-format) | FormTextField + autoTitle |
fieldHelpers.email() | Email input | FormTextField |
fieldHelpers.password() | Password input | FormTextField |
fieldHelpers.textarea() | Multi-line text | FormTextareaField |
fieldHelpers.select() | Single select | FormSelectField |
fieldHelpers.multiSelect() | Multi-select | FormMultiSelectField |
fieldHelpers.switch() | Toggle switch | FormSwitchField |
fieldHelpers.checkbox() | Checkbox | FormCheckboxField |
fieldHelpers.radio() | Radio group | FormRadioField |
fieldHelpers.date() | Date picker | FormDateField |
fieldHelpers.dateRange() | Date range | FormDateRangeField |
fieldHelpers.color() | Color picker | FormColorField |
fieldHelpers.richText() | Rich text | FormRichTextField |
fieldHelpers.tag() | Tag input | FormTagField |
fieldHelpers.cardPicker() | Card picker | FormCardPickerField |
fieldHelpers.audience() | Audience picker | FormAudiencePickerField |
fieldHelpers.prettyDesigner() | Visual identity | FormPrettyDesignerField |
fieldHelpers.custom() | Custom render | FormCustomField |
const fields = [
fieldHelpers.text('name', 'Name', { placeholder: 'Enter name...' }),
fieldHelpers.title('projectTitle', 'Project Title'), // Auto-title-case enabled
fieldHelpers.email('email', 'Email'),
fieldHelpers.password('password', 'Password'),
fieldHelpers.textarea('bio', 'Bio', { rows: 4 }),
fieldHelpers.select('country', 'Country', countryOptions),
fieldHelpers.multiSelect('tags', 'Tags', tagOptions),
fieldHelpers.switch('notifications', 'Enable Notifications'),
fieldHelpers.checkbox('terms', 'I agree to the terms'),
fieldHelpers.radio('priority', 'Priority', priorityOptions),
fieldHelpers.date('startDate', 'Start Date'),
fieldHelpers.dateRange('availability', 'Availability'),
fieldHelpers.color('brandColor', 'Brand Color'),
fieldHelpers.richText('content', 'Content'),
fieldHelpers.tag('keywords', 'Keywords'),
];Props Reference
Core Props
Essential props for form configuration and data handling.
| Prop | Type | Default | Description |
|---|---|---|---|
schema | z.ZodType | required | Zod schema for validation |
mode | 'submit' | 'auto-save' | 'wizard' | 'submit' | Form mode |
defaultValues | Partial<T> | - | Initial form values |
sections | FormSectionDefinition[] | - | Declarative sections |
children | ReactNode | - | JSX children |
inDialog | boolean | false | Optimize layout for dialog/modal use |
Event Handlers
Callbacks for form submission, auto-save, and navigation events.
| Prop | Type | Default | Description |
|---|---|---|---|
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.
| Prop | Type | Default | Description |
|---|---|---|---|
currentStep | number | - | Controlled current step |
showStepIndicator | boolean | true | Show step progress indicator |
allowStepJump | boolean | false | Allow clicking on step indicator to jump |
validateOnStepChange | boolean | true | Validate before advancing steps |
Quick Start
import { FormBlock, FormSection, FormGrid, FormTextField, fieldHelpers } 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>