# Form error summary

The form's problems listed at its top after a submit, each a link that moves focus to the control it concerns. Use it on a long form, where the first error may be off screen; on a short one the fields' own errors are enough. It shows what you pass, in the order of the form, and renders nothing when there are none.

It is an alert, so a screen reader reads it when it appears. Pair it with `Field`'s `attempt`, which moves focus to the first field with an error; the summary is then read, and the person is already where the first fix goes.

An error's shake (a brand's Pro `jolt` on this moment) is always beside the error's words, here the list of problems. A locked control's shake is a different moment, `refuse`, which shows why it can't be used; the two never share one.

- Registry name: `form-error-summary` (`@candor/form-error-summary`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/form-error-summary

## Install

```bash
npx candor-ui add form-error-summary
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/form-error-summary`.
It brings in: locale, motion, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { Field } from "@/components/candor/ui/field"
import { FormErrorSummary, type FormError } from "@/components/candor/ui/form-error-summary"
import { Input } from "@/components/candor/ui/input"
import { Button } from "@/components/candor/ui/button"

// Each problem links to its field by the Field's `id`; the summary lists them in the form's order.
export default function Example() {
  const [errors, setErrors] = React.useState<FormError[]>([])
  const [attempt, setAttempt] = React.useState(0)
  const errorFor = (id: string) => errors.find((e) => e.id === id)?.message
  return (
    <form
      noValidate
      className="flex w-full max-w-sm flex-col gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        const next: FormError[] = []
        if (!String(data.get("workspace")).trim()) next.push({ id: "workspace", message: "Enter a workspace name" })
        if (!String(data.get("owner")).trim()) next.push({ id: "owner", message: "Enter the owner's email address" })
        setErrors(next)
        setAttempt((n) => n + 1)
      }}
    >
      <FormErrorSummary errors={errors} />
      <Field id="workspace" label="Workspace name" required error={errorFor("workspace")} attempt={attempt}>
        <Input name="workspace" />
      </Field>
      <Field id="owner" label="Owner" required error={errorFor("owner")} attempt={attempt}>
        <Input name="owner" type="email" />
      </Field>
      <Button type="submit" className="self-start">
        Create workspace
      </Button>
    </form>
  )
}
```

## <FormErrorSummary>

Props:

- `errors: FormError[]`: The problems, in the order their fields appear.
- `title?: string | undefined`: The heading, in your own words. Defaults to "There is a problem" or "There are 3 problems".
- `className?: string | undefined`: Classes for the summary's box.

## Strings (`formErrorSummary`)

The fixed words, English by default. Override any of them under `formErrorSummary` in `<LocaleProvider strings={{ formErrorSummary: { … } }}>`; the table is exported as `formErrorSummaryStrings`.

- `title`: { one: "There is a problem", other: "There are {count} problems" }

## Types

### FormError

One problem in the form.

```ts
{
  /** The id of the control it concerns: a `Field`'s `id`. The link moves focus there. */
  id: string;
  /** What is wrong, the same words the field shows. */
  message: string;
}
```

## Motion

- **failed** (attention): One ring goes round the summary when a submit fails while the form is on screen: its problems go from none to some. A summary drawn with problems already listed shows them still, and a later submit that still fails doesn't play it again.

Set with `--cd-motion-form-error-summary-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
