Form error summary
ProThe 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.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1 and 2.6.2: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/form-error-summary
Brings in: heading-level, heading-scope, locale, motion, tokens, utils · npm: lucide-react@^1.67.0
Usage
"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>
| Prop | Type | Default | Description |
|---|---|---|---|
| errorsrequired | 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
The fixed words, in English by default. Change any of them under formErrorSummary in <LocaleProvider strings={{ … }}>; the table is exported as formErrorSummaryStrings.
| Key | English |
|---|---|
| title | { one: "There is a problem", other: "There are {count} problems" } |
Motion
| Moment | Family | What it does |
|---|---|---|
| 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.
Types
- FormError
- One problem in the form.
{ /** 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 from @/lib/candor/heading-level
- 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.
1 | 2 | 3 | 4 | 5 | 6