Form Builder
Build declarative, type-safe forms with automatic validation inspired by Filament PHP.
Interactive Demo
Try out the form builder below. Fill in the fields and click submit to see the form data:
Build declarative, type-safe forms with automatic validation inspired by Filament PHP.
Introduction
ShadPanel’s Form Builder provides a powerful, declarative API for creating forms with:
- ✅ Declarative API - Define forms with JSX components
- ✅ Automatic Validation - Built-in validation with error messages
- ✅ Type Safety - Full TypeScript support with generics
- ✅ State Management - Automatic form state handling
- ✅ Flexible Layout - Grid, sections, tabs, and more
- ✅ 12+ Field Types - Input, select, textarea, date, file upload, etc.
- ✅ Real-time Feedback - Validation on blur and submit
- ✅ Customizable - Extend with your own fields
Quick Start
Basic Form
'use client'
import { Form, FormInput, FormSelect } from '@/components/ui'
import { Button } from '@/components/ui/button'
export default function UserForm() {
return (
<Form
initialValues={{ name: '', email: '', role: 'user' }}
onSubmit={(values) => {
console.log(values)
}}
>
<FormInput accessor="name" label="Name" required />
<FormInput accessor="email" label="Email" type="email" required />
<FormSelect
accessor="role"
label="Role"
options={[
{ label: 'User', value: 'user' },
{ label: 'Admin', value: 'admin' },
]}
/>
<Button type="submit">Submit</Button>
</Form>
)
}With Layout
import { Form, FormInput, FormSection, FormGrid } from '@/components/ui'
export default function ProfileForm() {
return (
<Form initialValues={{ firstName: '', lastName: '', email: '' }}>
<FormSection
title="Personal Information"
description="Enter your personal details"
>
<FormGrid columns={2}>
<FormInput accessor="firstName" label="First Name" required />
<FormInput accessor="lastName" label="Last Name" required />
</FormGrid>
<FormInput accessor="email" label="Email" type="email" required />
</FormSection>
</Form>
)
}Core Concepts
Form Component
The Form component manages state, validation, and submission:
<Form
initialValues={{ email: '', password: '' }}
onSubmit={(values) => {
// Handle submission
}}
onChange={(values) => {
// Track changes
}}
>
{/* Fields */}
</Form>Default Styling:
The Form component includes automatic padding (px-8 pb-8) and spacing (space-y-6).
Accessor Pattern
Fields use the accessor prop to connect to form state:
<FormInput accessor="email" label="Email" />The accessor must match a key in initialValues:
<Form initialValues={{ email: '', name: '' }}>
<FormInput accessor="email" label="Email" /> {/* ✓ Valid */}
<FormInput accessor="name" label="Name" /> {/* ✓ Valid */}
<FormInput accessor="age" label="Age" /> {/* ✗ Invalid */}
</Form>TypeScript Support
Use generics for type safety:
interface FormData {
email: string
name: string
age: number
}
<Form<FormData>
initialValues={{ email: '', name: '', age: 0 }}
onSubmit={(values) => {
// values is typed as FormData
console.log(values.email) // ✓ TypeScript knows this is string
}}
>
<FormInput accessor="email" label="Email" />
<FormInput accessor="name" label="Name" />
<FormInput accessor="age" label="Age" type="number" />
</Form>Field Components
Text Input
<FormInput
accessor="name"
label="Full Name"
placeholder="John Doe"
required
helperText="Enter your full legal name"
/>Types: text, email, password, number, tel, url
Textarea
<FormTextarea
accessor="bio"
label="Biography"
placeholder="Tell us about yourself..."
rows={5}
required
/>Select
<FormSelect
accessor="country"
label="Country"
options={[
{ label: 'United States', value: 'us' },
{ label: 'Canada', value: 'ca' },
{ label: 'United Kingdom', value: 'uk' },
]}
placeholder="Select a country"
required
/>Checkbox
<FormCheckbox
accessor="terms"
label="I agree to the terms and conditions"
required
/>Toggle
<FormToggle
accessor="notifications"
label="Email Notifications"
helperText="Receive email updates"
/>Tags Input
<FormTagsInput
accessor="tags"
label="Tags"
placeholder="Add tags..."
/>Date Picker
<FormDatePicker
accessor="birthDate"
label="Birth Date"
required
/>Date Time Picker
<FormDateTimePicker
accessor="appointment"
label="Appointment"
required
/>File Upload
<FormFileUpload
accessor="avatar"
label="Profile Picture"
accept="image/*"
maxSize={5 * 1024 * 1024} // 5MB
/>Key-Value
<FormKeyValue
accessor="metadata"
label="Metadata"
keyPlaceholder="Key"
valuePlaceholder="Value"
/>Markdown Editor
<FormMarkdownEditor
accessor="content"
label="Content"
placeholder="Write in markdown..."
/>Rich Text Editor
<FormRichEditor
accessor="description"
label="Description"
/>Layout Components
FormGrid
Responsive grid layout:
<FormGrid columns={2}>
<FormInput accessor="firstName" label="First Name" />
<FormInput accessor="lastName" label="Last Name" />
</FormGrid>
{/* Responsive columns */}
<FormGrid columns={{ sm: 1, md: 2, lg: 3 }}>
<FormInput accessor="field1" label="Field 1" />
<FormInput accessor="field2" label="Field 2" />
<FormInput accessor="field3" label="Field 3" />
</FormGrid>FormSection
Group fields with title and description:
<FormSection
title="Account Information"
description="Manage your account settings"
>
<FormInput accessor="email" label="Email" />
<FormInput accessor="password" label="Password" type="password" />
</FormSection>FormFieldset
Group related fields:
<FormFieldset legend="Contact Information">
<FormInput accessor="email" label="Email" />
<FormInput accessor="phone" label="Phone" />
</FormFieldset>FormGroup
Simple field grouping:
<FormGroup>
<FormInput accessor="city" label="City" />
<FormInput accessor="zip" label="ZIP Code" />
</FormGroup>FormTabs
Organize forms into tabs:
<FormTabs
tabs={[
{
label: 'Profile',
content: (
<>
<FormInput accessor="name" label="Name" />
<FormInput accessor="email" label="Email" />
</>
),
},
{
label: 'Settings',
content: (
<>
<FormToggle accessor="notifications" label="Notifications" />
<FormSelect accessor="language" label="Language" options={[]} />
</>
),
},
]}
/>Validation
Built-in Validation
<FormInput
accessor="email"
label="Email"
required
type="email"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
minLength={5}
maxLength={100}
/>Validation Rules:
required- Field is requiredtype- HTML5 input types (email, url, number, etc.)pattern- Regex patternminLength/maxLength- String lengthmin/max- Number range
Custom Validation
<FormInput
accessor="username"
label="Username"
required
validate={(value) => {
if (value.length < 3) {
return 'Username must be at least 3 characters'
}
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
return 'Username can only contain letters, numbers, and underscores'
}
return true
}}
/>Error Messages
Validation errors display automatically:
// Show error on blur
<FormInput accessor="email" label="Email" required />
// Custom error message
<FormInput
accessor="email"
label="Email"
required
requiredMessage="Email address is required"
/>Form Submission
onSubmit Handler
<Form
initialValues={{ email: '', password: '' }}
onSubmit={async (values) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(values),
})
// Handle response
} catch (error) {
// Handle error
}
}}
>
{/* Fields */}
</Form>Submitting State
Access form state with context:
import { useFormContext } from '@/components/ui/form-builder'
function SubmitButton() {
const { isSubmitting } = useFormContext()
return (
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</Button>
)
}Form Reset
import { useFormContext } from '@/components/ui/form-builder'
function ResetButton() {
const { reset } = useFormContext()
return (
<Button type="button" onClick={reset}>
Reset Form
</Button>
)
}Complete Example
'use client'
import {
Form,
FormInput,
FormTextarea,
FormSelect,
FormCheckbox,
FormSection,
FormGrid,
} from '@/components/ui'
import { Button } from '@/components/ui/button'
interface UserFormData {
firstName: string
lastName: string
email: string
phone: string
role: string
bio: string
terms: boolean
}
export default function UserForm() {
return (
<Form<UserFormData>
initialValues={{
firstName: '',
lastName: '',
email: '',
phone: '',
role: 'user',
bio: '',
terms: false,
}}
onSubmit={async (values) => {
console.log('Submitting:', values)
// API call here
}}
onChange={(values) => {
console.log('Form changed:', values)
}}
>
<FormSection
title="Personal Information"
description="Enter your personal details"
>
<FormGrid columns={2}>
<FormInput
accessor="firstName"
label="First Name"
required
placeholder="John"
/>
<FormInput
accessor="lastName"
label="Last Name"
required
placeholder="Doe"
/>
</FormGrid>
<FormInput
accessor="email"
label="Email"
type="email"
required
placeholder="john@example.com"
/>
<FormInput
accessor="phone"
label="Phone"
type="tel"
placeholder="+1 (555) 000-0000"
/>
</FormSection>
<FormSection
title="Account Settings"
description="Configure your account"
>
<FormSelect
accessor="role"
label="Role"
options={[
{ label: 'User', value: 'user' },
{ label: 'Admin', value: 'admin' },
{ label: 'Moderator', value: 'moderator' },
]}
required
/>
<FormTextarea
accessor="bio"
label="Biography"
placeholder="Tell us about yourself..."
rows={5}
/>
<FormCheckbox
accessor="terms"
label="I agree to the terms and conditions"
required
/>
</FormSection>
<div className="flex gap-4">
<Button type="submit">Create User</Button>
<Button type="button" variant="outline">
Cancel
</Button>
</div>
</Form>
)
}Next Steps
- Field Reference - All available fields
- Layout Components - Form layouts
- Validation Guide - Custom validation
- Form API - Complete API reference
- Form Examples - Real-world examples