Overview
Get Started
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
export function FormDemo() {
const [loading, setLoading] = useState(false);
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
setLoading(true);
await new Promise((r) => setTimeout(r, 800));
setLoading(false);
alert(`Email: ${formData.get("email") || ""}`);
};
return (
<Form className="flex w-full max-w-64 flex-col gap-4" onSubmit={onSubmit}>
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input type="email" placeholder="you@example.com" required />
<FieldError>Please enter a valid email.</FieldError>
</Field>
<Button type="submit" disabled={loading} focusableWhenDisabled>
{loading && <Spinner />}
Submit
</Button>
</Form>
);
}Installation
import { Form as FormPrimitive } from "@base-ui/react/form";
function Form(props: React.ComponentProps<typeof FormPrimitive>) {
return <FormPrimitive data-slot="form" {...props} />;
}
export { Form, FormPrimitive };Usage
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";<Form
className="flex w-full flex-col gap-4"
onSubmit={(e) => {
/* handle submit */
}}>
<Field>
<FieldLabel>Email</FieldLabel>
<Input name="email" type="email" required />
<FieldError>Please enter a valid email.</FieldError>
</Field>
</Form>Examples
Submit with server function
"use client";
import { useActionState } from "react";
import { Button } from "@/components/ui/button";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
interface FormState {
serverErrors?: React.ComponentProps<typeof Form>["errors"];
}
export default function ActionStateForm() {
const [state, formAction, loading] = useActionState<FormState, FormData>(submitForm, {});
return (
<Form
errors={state.serverErrors}
action={formAction}
className="flex w-full max-w-64 flex-col gap-4"
>
<Field name="username">
<FieldLabel>Username</FieldLabel>
<Input type="username" required defaultValue="admin" placeholder="e.g. alice132" />
<FieldError />
</Field>
<Button type="submit" disabled={loading} focusableWhenDisabled>
{loading ? "Submitting..." : "Submit"}
</Button>
</Form>
);
}
async function submitForm(_previousState: FormState, formData: FormData) {
// Mimic a server response delay.
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
try {
const username = formData.get("username") as string | null;
if (username === "admin") {
return { success: false, serverErrors: { username: "'admin' is reserved for system use" } };
}
// 50% chance the username is taken
const success = Math.random() > 0.5;
if (!success) {
return {
serverErrors: { username: `${username} is unavailable` },
};
}
} catch {
return { serverErrors: { username: "A server error has occurred" } };
}
return {};
}Submit form values as JavaScript object
<Form
onFormSubmit={async (formValues: { id: string; quantity: number }) => {
const payload = {
product_id: formValues.id,
order_quantity: formValues.quantity,
};
const response = await fetch("https://api.example.com", {
method: "POST",
body: payload,
});
}}
/>Using with zod
"use client";
import { useState } from "react";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Form, type FormPrimitive } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const formSchema = z.object({
name: z.string().min(1, "Name is required"),
age: z.coerce.number("Age must be a number").positive("Age must be a positive number"),
});
async function submitForm(formValues: FormPrimitive.Values) {
const result = formSchema.safeParse(formValues);
if (!result.success) {
return {
errors: z.flattenError(result.error).fieldErrors,
};
}
return {
errors: {},
};
}
export function FormDemo() {
const [errors, setErrors] = useState({});
return (
<Form
errors={errors}
onFormSubmit={async (formValues) => {
const response = await submitForm(formValues);
setErrors(response.errors);
}}
className="flex w-full max-w-64 flex-col gap-4"
>
<Field name="name">
<FieldLabel>Name</FieldLabel>
<Input type="text" placeholder="Enter your name" required />
<FieldError>{formSchema.shape.name.safeParse("").error?.issues[0].message}</FieldError>
</Field>
<Field name="age">
<FieldLabel>Age</FieldLabel>
<Input type="number" placeholder="Enter your age" required />
<FieldError>{formSchema.shape.age.safeParse("").error?.issues[0].message}</FieldError>
</Field>
<Button type="submit">Submit</Button>
</Form>
);
}