# Form

Validates its [Fields](/components/field.md) together on submit and focuses the first invalid one. It adds no layout; arrange its content with [Grid](/components/grid.md) and [Stack](/components/stack.md).

```tsx
// demo.tsx
import {Button, Field, Form, Grid, Input, Stack, Text} from '@earendil-works/elanor-react';
import {useState} from 'react';

export default function FormDemo() {
  const [message, setMessage] = useState('');

  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Form onFormSubmit={(values) => setMessage(`Invite sent to ${values.email}.`)}>
          <Stack gap="medium" align="stretch">
            <Field.Root name="name">
              <Field.Label>Name</Field.Label>
              <Input autoComplete="name" required />
              <Field.Error match="valueMissing">Enter a name.</Field.Error>
            </Field.Root>
            <Field.Root name="email">
              <Field.Label>Email</Field.Label>
              <Input type="email" autoComplete="email" required />
              <Field.Error match="valueMissing">Enter an email address.</Field.Error>
              <Field.Error match="typeMismatch">Enter a valid email address.</Field.Error>
            </Field.Root>
            <Stack direction="row">
              <Button type="submit">Send invite</Button>
            </Stack>
            <Text role="status">{message}</Text>
          </Stack>
        </Form>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## Usage

```tsx
import {Button, Field, Form, Input} from '@earendil-works/elanor-react';

<Form onFormSubmit={(values) => save(values)}>
  <Field.Root name="email">
    <Field.Label>Email</Field.Label>
    <Input type="email" required />
    <Field.Error />
  </Field.Root>
  <Button type="submit">Save</Button>
</Form>;
```

`onFormSubmit` receives the values keyed by field `name` and replaces the native submit. For a native submission, use `onSubmit` or `action`.

## Server errors

Pass errors from the server to `errors`, keyed by field `name`. Each shows in the field's `Field.Error` until the value changes. In the demo, alex@example.com is taken.

```tsx
// server-errors.tsx
import {
  Button,
  Field,
  Form,
  Grid,
  Input,
  Stack,
  type FormProps,
} from '@earendil-works/elanor-react';
import {useState} from 'react';

export default function FormServerErrors() {
  const [errors, setErrors] = useState<FormProps['errors']>({});

  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Form
          errors={errors}
          onFormSubmit={(values) => {
            // Stands in for a server response.
            setErrors(
              values.email === 'alex@example.com'
                ? {email: 'This email already has an account.'}
                : {},
            );
          }}
        >
          <Stack gap="medium" align="stretch">
            <Field.Root name="email">
              <Field.Label>Email</Field.Label>
              <Input type="email" autoComplete="email" defaultValue="alex@example.com" required />
              <Field.Error />
            </Field.Root>
            <Stack direction="row">
              <Button type="submit">Create account</Button>
            </Stack>
          </Stack>
        </Form>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## API

### `Form`

Validates its Fields together on submit and focuses the first invalid one.

Accepts Base UI [`Form`](https://base-ui.com/react/components/form.md) props.
