Form

A native <form> that consolidates its Fields' validation: submit runs every check, focuses the first invalid field, and hands you typed values once everything passes.

Submit empty to watch the first invalid field take focus and its error shake in.

tsx
"use client"

import * as React from "react"

import { Button } from "@/registry/seam/ui/button"
import { Field, FieldError, FieldLabel } from "@/registry/seam/ui/field"
import { Form } from "@/registry/seam/ui/form"
import { Input } from "@/registry/seam/ui/input"

// Native constraints (required, type, minLength) drive validation; submitting
// with problems focuses the first invalid field and shakes its error in.
// `onFormSubmit` only fires once everything passes.
export default function FormDemo() {
  const [signedInAs, setSignedInAs] = React.useState<string | null>(null)

  return (
    <Form<{ email: string; password: string }>
      className="max-w-xs"
      onFormSubmit={(values) => setSignedInAs(values.email)}
    >
      <Field name="email">
        <FieldLabel>Email</FieldLabel>
        <Input type="email" required placeholder="you@company.com" />
        <FieldError match="valueMissing">Enter your email.</FieldError>
        <FieldError match="typeMismatch">
          That doesn&apos;t look like an email.
        </FieldError>
      </Field>
      <Field name="password">
        <FieldLabel>Password</FieldLabel>
        <Input type="password" required minLength={8} placeholder="••••••••" />
        <FieldError match="valueMissing">Enter your password.</FieldError>
        <FieldError match="tooShort">At least 8 characters.</FieldError>
      </Field>
      <Button type="submit">Sign in</Button>
      {signedInAs && (
        <p className="text-muted-foreground text-sm">
          Signed in as {signedInAs}.
        </p>
      )}
    </Form>
  )
}
bunx --bun @seamui/cli@latest add form

API

PropTypeDefaultDescription
onFormSubmit(values, eventDetails) => voidFires only when every field passes — values are keyed by each Field's name.
errorsRecord<string, string | string[]>External (server) errors; a field's entry clears as soon as its value changes.
validationMode"onSubmit" | "onBlur" | "onChange""onSubmit"Default validation timing for every child Field.
actionsRef{ validate(fieldName?) }Imperatively validate all fields, or one by name.

Plus all Base UI Form props — the component is generic: <Form<{ email: string }>> types onFormSubmit's values.

Notes

  • The form itself stays still — motion lives on the Fields inside it (error shake, destructive wells) and on the submit Button's press depth.
  • Native constraint attributes (required, minLength, type) are the validation source; pair each with a FieldError match for custom copy.

Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.