Components
Form
Native form primitive with lightweight submit validation and observable submit state.
When to Use
Use Form when you want native form submission plus Atom-owned submitting,
submitted, and validation state. A plain form is enough when those states are
not needed. Form does not provide a schema, field messages, or server
validation; combine it with Field and your application validation.
Features
- Renders a native
formand preserves native attributes. - Supports synchronous or asynchronous pre-submit validation.
- Optionally prevents the browser's default submit navigation.
- Tracks submitting, submitted, and failed-validation state.
- Resets Atom state after an uncancelled native reset.
- Exposes state through
useFormContextand data attributes.
Import
import { Form } from "@flowstack-ui/atom";
Anatomy
<Form.Root />
useFormContext()
API Reference
Root
Renders the native form, runs optional validation before onSubmit, and
provides submission state to descendants.
| Prop | Type | Default |
|---|---|---|
onSubmit | (event) => void | Promise<void> | - |
onReset | (event) => void | - |
preventDefaultOnSubmit | boolean | false |
validateOnSubmit | (event) => boolean | Promise<boolean> | - |
asChild | boolean | false |
render | RenderProp | - |
Async validation always prevents default navigation so validation can finish.
A synchronous false result also prevents submission and marks the Form
invalid. For an async onSubmit without async validation, set
preventDefaultOnSubmit when browser navigation is not wanted. Rejected submit
handlers leave submitted state false; the error is not rethrown by Form.
| Data attribute | Values |
|---|---|
[data-slot] | "form" |
[data-submitting] | Present while validation or async submission is pending |
[data-submitted] | Present after the handler completes successfully |
[data-invalid] | Present after validateOnSubmit returns false |
useFormContext
Returns { submitting, submitted, invalid } for custom status parts. It must
be called below Root.
Examples
Native Submission State
import type { FormEvent } from "react";
import { Form, useFormContext } from "@flowstack-ui/atom";
function SubmitButton() {
const { submitting } = useFormContext();
return <button type="submit" disabled={submitting}>Save profile</button>;
}
export function ProfileForm() {
async function saveProfile(event: FormEvent<HTMLFormElement>) {
const data = new FormData(event.currentTarget);
await fetch("/api/profile", { method: "POST", body: data });
}
return (
<Form.Root preventDefaultOnSubmit onSubmit={saveProfile}>
<label>
Display name
<input name="displayName" required />
</label>
<SubmitButton />
</Form.Root>
);
}
Async Validation
import type { FormEvent } from "react";
import { Form } from "@flowstack-ui/atom";
export function InvitationForm() {
async function emailIsAvailable(event: FormEvent<HTMLFormElement>) {
const data = new FormData(event.currentTarget);
const response = await fetch(`/api/email/${String(data.get("email"))}`);
return response.ok;
}
return (
<Form.Root validateOnSubmit={emailIsAvailable} onSubmit={() => console.log("Valid")}>
<label>Email <input name="email" type="email" required /></label>
<button type="submit">Invite</button>
<button type="reset">Reset</button>
</Form.Root>
);
}
Accessibility
Form preserves native form semantics and owns no special keyboard model. Consumers must label controls, expose errors, move focus when appropriate, and announce asynchronous results. Use Field and Fieldset for accessible names, descriptions, and visible validation messages.
Changelog
See CHANGELOG.md.