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 form and 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 useFormContext and 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.

PropTypeDefault
onSubmit(event) => void | Promise<void>-
onReset(event) => void-
preventDefaultOnSubmitbooleanfalse
validateOnSubmit(event) => boolean | Promise<boolean>-
asChildbooleanfalse
renderRenderProp-

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 attributeValues
[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.