Form

Groups fields and actions, routes server errors to fields, and disables fields while submission is pending.

import { Form } from "@usefragments/ui";Source

Preview

<Form onSubmit={(e) => { e.preventDefault(); }}>
  <Grid columns={2} gap="md">
    <Field name="firstName">
      <Field.Label>First name</Field.Label>
      <Field.Control>
        <Input placeholder="Jane" />

Usage

Use when

  • The server rejects a submission and each reason must reach its own field
  • Client and server validation live in the same form

Avoid when

  • Client-side validation only (a native form plus Field is enough)
  • Layout without submission (use Grid or Card)

Implementation notes

  • errors is keyed by field name: Record<string, string | string[]>
  • A field's server error clears as soon as the person edits that field
  • A Field picks up its error by matching name
  • Leave Field.Error unmatched so it shows whatever the server sent
  • Put a Grid inside Form or Fieldset for multi-column layouts
  • Buttons go in Form.Actions so they stay right-aligned
  • Set pending while the submit is in flight, and give the submit Button pending too

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Form once, then compose its parts with dot notation.

  • Form.Actions

Paired name fields, then full-width rows and a right-aligned submit.

<Form onSubmit={(e) => { e.preventDefault(); }}>
  <Grid columns={2} gap="md">
    <Field name="firstName">
      <Field.Label>First name</Field.Label>
      <Field.Control>
        <Input placeholder="Jane" />
      </Field.Control>
    </Field>
    <Field name="lastName">
      <Field.Label>Last name</Field.Label>
      <Field.Control>
        <Input placeholder="Doe" />
      </Field.Control>
    </Field>
    <Grid.Item colSpan="full">
      <Field name="email">
        <Field.Label>Email</Field.Label>
        <Field.Control>

Examples

Profile settings

Fieldsets split a long form into sections the user can scan.

<Form onSubmit={(e) => { e.preventDefault(); }}>
  <Fieldset>
    <Fieldset.Legend>Profile</Fieldset.Legend>
    <Grid columns={2} gap="md">
      <Field name="displayName">
        <Field.Label>Display Name</Field.Label>

Contact form

Mixed control types in one submission flow.

<Form onSubmit={(e) => { e.preventDefault(); }}>
  <Grid columns={2} gap="md">
    <Field name="name">
      <Field.Label>Name</Field.Label>
      <Field.Control>
        <Input placeholder="Your name" />

With server errors

Errors from the server land on the fields that caused them.

<Form errors={{ username: 'Username is already taken', email: 'Email is already registered' }}>
  <Grid columns={2} gap="md">
    <Field name="username">
      <Field.Label>Username</Field.Label>
      <Field.Control>
        <Input defaultValue="janedoe" />

Pending

Fields lock and the submit shows its working state while the request runs.

<Form pending onSubmit={(e) => { e.preventDefault(); }}>
  <Field name="email">
    <Field.Label>Email</Field.Label>
    <Input type="email" defaultValue="jane@example.com" />
  </Field>
  <Form.Actions>

API reference

Component props
Prop
Type
Default
Description
Actions.childrenRequiredReact.ReactNodeNot set—
childrenRequiredReact.ReactNodeNot setForm content
errorsRecord<string, string | string[]> | undefinedNot setServer-side errors keyed by field name
onSubmit((event: React.FormEvent<HTMLFormElement>) => void) | undefinedNot setForm submission handler
pendingboolean | undefinedfalseA submit is in flight: the form is aria-busy and every control inside is locked until it settles
validationMode"onBlur" | "onChange" | "onSubmit" | undefinedonBluronChangeonSubmitNot setWhen field validation should run

Accessibility

  • Renders a real form element
  • Errors link back to their field via aria-describedby
  • pending sets aria-busy and disables every control inside
  • FieldsiblingContains Field components for error distribution
  • FieldsetsiblingUse Fieldset to group fields within a Form
  • ButtonsiblingUse Button type="submit" for form submission

Next steps