Skip to main content

Form

A form container with validation support, built on Base UI’s Form primitive.

Base UI Primitive

Built on @base-ui/react/form

Import

import { Form } from "@soft-ui/react/form"

Usage

import { Form } from "@soft-ui/react/form"
import { Field } from "@soft-ui/react/field"
import { Input } from "@soft-ui/react/input"
import { Button } from "@soft-ui/react/button"

export default function Example() {
  return (
    <Form onSubmit={(e) => {
      e.preventDefault()
      console.log(new FormData(e.currentTarget))
    }}>
      <Field label="Name">
        <Input name="name" required />
      </Field>
      <Field label="Email">
        <Input name="email" type="email" required />
      </Field>
      <Button type="submit">Submit</Button>
    </Form>
  )
}

Props

onSubmit
(event: React.FormEvent<HTMLFormElement>) => void
Form submission handler.
method
'get' | 'post'
HTTP method for form submission.
action
string
URL to submit form data.
encType
string
Encoding type for form data.
noValidate
boolean
Disables browser validation.
className
string
Additional CSS classes. Default: flex flex-col gap-[var(--space-24)]
children
React.ReactNode
required
Form fields and controls.

Examples

Basic Form

import { Form } from "@soft-ui/react/form"
import { Field } from "@soft-ui/react/field"
import { Input } from "@soft-ui/react/input"
import { Button } from "@soft-ui/react/button"

export default function BasicForm() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    console.log(Object.fromEntries(formData))
  }

  return (
    <Form onSubmit={handleSubmit}>
      <Field label="Name">
        <Input name="name" required />
      </Field>
      <Field label="Email">
        <Input name="email" type="email" required />
      </Field>
      <Button type="submit">Submit</Button>
    </Form>
  )
}

With Validation

import { Form } from "@soft-ui/react/form"
import { Field } from "@soft-ui/react/field"
import { Input } from "@soft-ui/react/input"
import { Button } from "@soft-ui/react/button"
import { useState } from "react"

export default function WithValidation() {
  const [errors, setErrors] = useState<Record<string, string>>({})

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    const data = Object.fromEntries(formData)
    
    // Validation
    const newErrors: Record<string, string> = {}
    if (!data.name) newErrors.name = "Name is required"
    if (!data.email) newErrors.email = "Email is required"
    if (data.email && !data.email.toString().includes("@")) {
      newErrors.email = "Invalid email address"
    }
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }
    
    setErrors({})
    console.log("Form submitted:", data)
  }

  return (
    <Form onSubmit={handleSubmit}>
      <Field 
        label="Name"
        error={errors.name}
      >
        <Input name="name" />
      </Field>
      <Field 
        label="Email"
        error={errors.email}
      >
        <Input name="email" type="email" />
      </Field>
      <Button type="submit">Submit</Button>
    </Form>
  )
}

With Multiple Field Types

import { Form } from "@soft-ui/react/form"
import { Field } from "@soft-ui/react/field"
import { Input } from "@soft-ui/react/input"
import { Textarea } from "@soft-ui/react/textarea"
import { Select } from "@soft-ui/react/select"
import { Checkbox } from "@soft-ui/react/checkbox"
import { Button } from "@soft-ui/react/button"

export default function MultipleFieldTypes() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    console.log(Object.fromEntries(formData))
  }

  return (
    <Form onSubmit={handleSubmit}>
      <Field label="Name">
        <Input name="name" required />
      </Field>
      
      <Field label="Country">
        <Select.Root name="country">
          <Select.Trigger>
            <Select.Value placeholder="Select a country" />
            <Select.Icon />
          </Select.Trigger>
          <Select.Portal>
            <Select.Positioner>
              <Select.Popup>
                <Select.List>
                  <Select.Item value="us">
                    <Select.ItemText>United States</Select.ItemText>
                    <Select.ItemIndicator />
                  </Select.Item>
                  <Select.Item value="ca">
                    <Select.ItemText>Canada</Select.ItemText>
                    <Select.ItemIndicator />
                  </Select.Item>
                </Select.List>
              </Select.Popup>
            </Select.Positioner>
          </Select.Portal>
        </Select.Root>
      </Field>
      
      <Field label="Message">
        <Textarea name="message" rows={4} />
      </Field>
      
      <Checkbox 
        name="terms" 
        label="I agree to the terms and conditions"
      />
      
      <Button type="submit">Submit</Button>
    </Form>
  )
}

With Custom Layout

import { Form } from "@soft-ui/react/form"
import { Field } from "@soft-ui/react/field"
import { Input } from "@soft-ui/react/input"
import { Button } from "@soft-ui/react/button"

export default function CustomLayout() {
  return (
    <Form className="space-y-6">
      <div className="grid grid-cols-2 gap-4">
        <Field label="First name">
          <Input name="firstName" />
        </Field>
        <Field label="Last name">
          <Input name="lastName" />
        </Field>
      </div>
      
      <Field label="Email">
        <Input name="email" type="email" />
      </Field>
      
      <div className="flex justify-end gap-2">
        <Button variant="secondary">Cancel</Button>
        <Button type="submit">Submit</Button>
      </div>
    </Form>
  )
}

Build docs developers (and LLMs) love