Form

Validates fields and displays server errors.

"use client"

import { useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Form } from "@/components/ui/form"
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

const styles = stylex.create({ form: { width: "20rem" } })

export default function FormDemo() {
  const [errors, setErrors] = useState<Record<string, string>>({})
  const [submitted, setSubmitted] = useState(false)
  return (
    <Form
      errors={errors}
      xstyle={styles.form}
      onFormSubmit={(values) => {
        if (values.username === "admin") {
          setErrors({ username: "This username is taken." })
          setSubmitted(false)
        } else {
          setErrors({})
          setSubmitted(true)
        }
      }}
    >
      <FieldGroup>
        <Field name="username">
          <FieldLabel>Username</FieldLabel>
          <Input required placeholder="Try admin" />
          <FieldError />
        </Field>
        <Button type="submit">Save</Button>
        {submitted && <output>Profile saved.</output>}
      </FieldGroup>
    </Form>
  )
}

Installation

Install the following dependencies:

pnpm add @base-ui/react @stylexjs/stylex

Set up the token layer and StyleX compiler from Installation, including state-styles.ts.

Copy and paste the following code into your project.

components/ui/form.tsx
"use client"

import type { CSSProperties } from "react"
import { Form as FormPrimitive } from "@base-ui/react/form"
import type { StyleXStyles } from "@stylexjs/stylex"
import { stateStyles } from "@/lib/state-styles"

function Form({
  xstyle,
  className,
  style,
  ...props
}: Omit<FormPrimitive.Props, "className" | "style"> & {
  xstyle?: StyleXStyles
  className?: string
  style?: CSSProperties
}) {
  return (
    <FormPrimitive
      data-slot="form"
      {...props}
      {...stateStyles(() => [xstyle], { className, style })}
    />
  )
}
export { Form }

Update the import paths to match your project setup.

Usage

import { Form } from "@/components/ui/form"
import { Field, FieldError, FieldLabel } from "@/components/ui/field"

import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

;<Form onFormSubmit={(values) => console.log(values)}>
  <Field name="username">
    <FieldLabel>Username</FieldLabel>
    <Input required />
    <FieldError />
  </Field>
  <Button type="submit">Save</Button>
</Form>

Anatomy

Form contains named Field controls and a submit button.

Server errors

Pass server messages through errors, keyed by field name. Edit the field to clear its error. Submit admin in the demo to receive a simulated server error.

API reference

Accepts Base UI Form props, including render, plus xstyle, className and inline style.

Form

PropTypeDefault
errorsRecord<string, string | string[]>—
validationMode"onSubmit" | "onBlur" | "onChange""onSubmit"
onFormSubmit(values, eventDetails) => void—
actionsRefRefObject<Form.Actions | null>—

onFormSubmit receives valid values and prevents native navigation. Name each Field to include it in the submitted values.

Data attributes

AttributeOnEffect
data-slot="form"formForm anatomy