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