# 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: heading-level, heading-scope, 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".
- `headingLevel?: HeadingLevel | undefined`: The level of the heading. Left out, it is one below the nearest `HeadingScope`, else 2 (a fixed `<h2>` before 5.0.0): in a dialog, under the dialog's title, it is an `<h3>`.
- `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;
}
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### HeadingLevel

Import: `import type { HeadingLevel } from "@/lib/candor/heading-level"` (item `heading-level`, https://candor.themethodic.com/docs/heading-level.md)

A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.

```ts
1 | 2 | 3 | 4 | 5 | 6
```

## 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`.
