Marble UI
← Components

Form

Preview: Validation

Native <form> with React Aria validation: field-level `validate`, `isRequired`, and server errors via `validationErrors={{ fieldName: 'message' }}`.

Import

import { Form, FormActions } from '@marbleui/ui'

Props

<Form>

PropTypeDefaultDescription
layout'stack' | 'grid''stack'

Also accepts React Aria props (91).

<FormActions>

PropTypeDefaultDescription
children *requiredReactNode—
classNamestring—

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitive: React Aria Form. Follow the primitive's docs for the full keyboard map.

Usage notes

Native <form> with React Aria validation. Fields validate on blur and on submit; the first invalid field receives focus.

<Form onSubmit={submit} validationErrors={serverErrors}>
  <TextField name="email" label="Email" type="email" isRequired />
  <FormActions><Button type="submit">Save</Button></FormActions>
</Form>
  • validationErrors={{ email: 'Already taken' }} shows server-side errors on the matching name.
  • layout="stack" stacks fields; use Fieldset to group related fields under a legend.
  • Read values from FormData in onSubmit (call e.preventDefault() for client-side handling).
Example source · show
import { useState } from 'react'
import { Button } from '../button'
import { Checkbox } from '../checkbox'
import { Select, SelectItem } from '../select'
import { TextField } from '../text-field'
import { Form, FormActions } from './form'

function SignUp() {
  const [errors, setErrors] = useState<Record<string, string>>({})
  const [pending, setPending] = useState(false)
  return (
    <Form
      className="w-80"
      validationErrors={errors}
      onSubmit={(e) => {
        e.preventDefault()
        const data = Object.fromEntries(new FormData(e.currentTarget))
        setPending(true)
        setTimeout(() => {
          setPending(false)
          setErrors(data.email === 'taken@acme.com' ? { email: 'An account with this email already exists.' } : {})
        }, 700)
      }}
    >
      <TextField name="name" label="Full name" isRequired />
      <TextField name="email" type="email" label="Work email" isRequired description='Try "taken@acme.com" for a server error.' />
      <Checkbox name="terms" value="yes" isRequired>I agree to the terms</Checkbox>
      <FormActions>
        <Button type="reset" variant="ghost">Reset</Button>
        <Button type="submit" isPending={pending}>Create account</Button>
      </FormActions>
    </Form>
  )
}

export const styles = {
  Validation: SignUp,
  Grid: () => (
    <Form layout="grid" className="w-[30rem]" onSubmit={(e) => e.preventDefault()}>
      <TextField name="first" label="First name" />
      <TextField name="last" label="Last name" />
      <TextField name="company" label="Company" className="col-span-2" />
      <Select name="size" label="Company size" className="col-span-2" defaultSelectedKey="11">
        <SelectItem id="1">1–10</SelectItem>
        <SelectItem id="11">11–50</SelectItem>
        <SelectItem id="51">51–200</SelectItem>
      </Select>
      <FormActions>
        <Button type="submit">Save</Button>
      </FormActions>
    </Form>
  ),
}

Source: packages/ui/src/form/form.tsx

Catalog

Category
inputs
Priority
Core
Primitive
react-aria
React Aria
Form
Base UI
Form