Form

A native form element with consolidated error handling and validation.
1<Form
2 onSubmit={(e) => {
3 e.preventDefault();
4 alert("Submitted!");
5 }}
6>
7 <Field label="Email" required>
8 <InputField type="email" placeholder="Enter email" />
9 </Field>
10 <Field label="Message" optional>
11 <TextArea placeholder="Enter message" />
12 </Field>
13 <Button type="submit">Submit</Button>
14</Form>

Anatomy

Import and assemble the component:

1import { Form, Field, InputField, Button } from "@raystack/apsara";
2
3<Form onSubmit={handleSubmit}>
4 <Field label="Email">
5 <InputField placeholder="Enter email" />
6 </Field>
7 <Button type="submit">Submit</Button>
8</Form>

API Reference

Prop

Type

Error Handling

Form supports two approaches to displaying errors, depending on how you build your form.

Props API (Simple)

Pass error strings directly to the Field error prop. This works well with custom validation logic or external form libraries like React Hook Form.

1(function PropsApiErrorExample() {
2 const [errors, setErrors] = useState({});
3
4 function handleSubmit(e) {
5 e.preventDefault();
6 const fd = new FormData(e.currentTarget);
7 const errs = {};
8 if (!fd.get("name")) errs.name = "Name is required";
9 if (!fd.get("email")) errs.email = "Email is required";
10 setErrors(errs);
11 }
12
13 return (
14 <Form onSubmit={handleSubmit}>
15 <Field label="Name" required error={errors.name}>

Sub-component API (Constraint Validation)

Use Field.Control with native HTML validation attributes and Field.Error with match to show specific messages for each constraint. The Form handles validation automatically on submit.

Available match values correspond to the browser ValidityState properties:

match valueTriggered by
"valueMissing"required attribute
"typeMismatch"type="email", type="url"
"patternMismatch"pattern attribute
"tooShort"minLength attribute
"tooLong"maxLength attribute
"rangeUnderflow"min attribute
"rangeOverflow"max attribute
"stepMismatch"step attribute
"customError"validate prop on Field
1<Form
2 onSubmit={(e) => {
3 e.preventDefault();
4 alert("Valid!");
5 }}
6>
7 <Field name="email">
8 <Field.Label>Email</Field.Label>
9 <Field.Control type="email" required placeholder="Enter email" />
10 <Field.Error match="valueMissing">Email is required</Field.Error>
11 <Field.Error match="typeMismatch">Please enter a valid email</Field.Error>
12 </Field>
13 <Field name="age">
14 <Field.Label>Age</Field.Label>
15 <Field.Control

Custom Validation

Use the validate prop on Field to run custom logic. Return an error string to fail, or null to pass. The error renders via Field.Error match="customError".

1(function CustomValidationExample() {
2 return (
3 <Form
4 onSubmit={(e) => {
5 e.preventDefault();
6 alert("Valid!");
7 }}
8 >
9 <Field
10 name="username"
11 validate={(value) => {
12 if (!value) return "Username is required";
13 if (String(value).length < 3) return "Must be at least 3 characters";
14 if (!/^[a-z0-9_]+$/.test(String(value)))
15 return "Only lowercase letters, numbers, and underscores";

Server-Side Errors

Pass server validation errors to the Form errors prop. Keys must match the name prop on each Field. Errors clear automatically when the user modifies the corresponding field.

1(function ServerErrorExample() {
2 const [errors, setErrors] = useState({});
3
4 async function handleSubmit(e) {
5 e.preventDefault();
6 // Simulate server validation
7 setErrors({ email: "This email is already taken" });
8 }
9
10 return (
11 <Form onSubmit={handleSubmit} errors={errors}>
12 <Field name="email" label="Email" required>
13 <InputField type="email" placeholder="Enter email" />
14 </Field>
15 <Button type="submit">Submit</Button>

Examples

Basic Form

A simple form with submit handling.

1<Form
2 onSubmit={(e) => {
3 e.preventDefault();
4 alert("Submitted!");
5 }}
6>
7 <Field label="Name" required>
8 <InputField placeholder="Enter your name" />
9 </Field>
10 <Button type="submit">Submit</Button>
11</Form>

With Fields

Form with multiple fields using the Field component.

1<Form
2 onSubmit={(e) => {
3 e.preventDefault();
4 }}
5>
6 <Field label="First Name" required>
7 <InputField placeholder="John" />
8 </Field>
9 <Field label="Last Name" required>
10 <InputField placeholder="Doe" />
11 </Field>
12 <Field label="Email" required description="We'll send a confirmation email">
13 <InputField type="email" placeholder="john@example.com" />
14 </Field>
15 <Field label="Bio" optional>

Validation Modes

Control when validation occurs: onSubmit (default), onBlur, or onChange.

1<Form validationMode="onSubmit">
2 <Field name="email">
3 <Field.Label>Email</Field.Label>
4 <Field.Control type="email" required placeholder="Validated on submit" />
5 <Field.Error match="valueMissing">Email is required</Field.Error>
6 <Field.Error match="typeMismatch">Enter a valid email</Field.Error>
7 </Field>
8 <Button type="submit">Submit</Button>
9</Form>

React Hook Form

Use register() for text inputs and Controller for custom controls. Pass errors.field?.message to Field's error prop.

1(function RHFExample() {
2 const {
3 register,
4 control,
5 handleSubmit,
6 formState: { errors },
7 } = useForm({
8 defaultValues: { name: "", email: "", role: "", notifications: true },
9 });
10
11 return (
12 <Form onSubmit={handleSubmit((data) => alert(JSON.stringify(data)))}>
13 <Field label="Name" required error={errors.name?.message}>
14 <InputField
15 {...register("name", { required: "Name is required" })}

Important Notes

Wrap interactive controls in Field

Components like Switch and Checkbox internally register with the Form validation system. They must be placed inside a <Field> when used within a <Form>, even if you don't need a label or error message. Without a Field wrapper, they use a shared default context that interferes with form submission.

1{/* Correct */}
2<Field label="Notifications">
3 <Switch checked={value} onCheckedChange={onChange} />
4</Field>
5
6{/* Incorrect - breaks Form validation */}
7<Switch checked={value} onCheckedChange={onChange} />

Constraint validation vs. external validation

Form provides built-in constraint validation using native HTML attributes (required, type, pattern, etc.) with Field.Control and Field.Error match="...". This is the simplest approach when you don't need a form library.

When using React Hook Form or similar, use the props API instead: pass error={errors.field?.message} to Field and let the library handle validation. Both approaches work with <Form>.

Accessibility

  • Renders a native <form> element with noValidate (validation is handled in JavaScript, not by browser tooltips)
  • Works with native constraint validation attributes (required, pattern, minLength, etc.)
  • Compatible with React Hook Form, TanStack Form, and other form libraries
  • Error messages are automatically associated with controls via aria-describedby