Skip to main content

Form

Distributes server-side validation errors to its Field components.

import { Form } from "@/fragments/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" />

Installation

npx @usefragments/cli add form

Writes src/fragments/ui/components/Form plus what it imports, the globals stylesheet, and .fragments/registry-lock.json, then prints the packages to add. No account, no key.

Your own registry: publish from Fragments Cloud and add --registry org/name. Connect a workspace.

Anatomy

Form is a compound: import the root and reach its parts through 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" />

API

Component props
Prop
Type
Default
Description
childrenRequirednodeNot setForm content
errorsobjectNot setServer-side errors keyed by field name
onSubmitfunctionNot setForm submission handler (preferred)
onFormSubmitfunctionNot setDeprecated alias for onSubmit
onClearErrorsfunctionNot setCalled with field name when errors should be cleared
validationModeenumonSubmitonBluronChangeNot setWhen field validation should run

Accessibility

  • Renders a real form element
  • Errors link back to their field via aria-describedby

Guidance

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)
  • FieldsiblingContains Field components for error distribution
  • FieldsetsiblingUse Fieldset to group fields within a Form
  • ButtonsiblingUse Button type="submit" for form submission

Next steps