Form Error
Form-level submission error banner — appears when submit is blocked by field validation failures. Pairs with Field, FieldError, and Input[aria-invalid] to deliver the complete form validation pattern.
Submit-Error Banner
The FormError banner appears inside the form card when submission is blocked. It uses a left-border destructive accent on a softly tinted surface — the color signals urgency; the role="alert" announces it immediately to assistive technology.
Please fix the errors below before continuing
2 fields need your attention.
Minimal Banner
The icon is optional. Without it, the left-border and tinted surface still clearly signal an error.
Please fix the errors below before continuing
Full Validation Pattern
The complete pattern combines:
FormError— form-level submission bannerField+FieldError— field-level inline errorsInput[aria-invalid]— error-state input treatment
Try submitting the form empty or with an invalid email to see the full pattern.
Installation
npx visor add form-errorThis copies two files into your project:
components/ui/form-error/form-error.tsx— the componentcomponents/ui/form-error/form-error.module.css— the styles
Usage
import { FormError, FormErrorTitle, FormErrorDescription } from '@/components/ui/form-error/form-error';
import { Field, FieldLabel, FieldError } from '@/components/ui/field/field';
import { Input } from '@/components/ui/input/input';
export default function InviteForm() {
const [nameError, setNameError] = React.useState<string | null>(null);
const [submitError, setSubmitError] = React.useState(false);
const nameRef = React.useRef<HTMLInputElement>(null);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const ne = !nameValue.trim() ? 'Full name is required' : null;
setNameError(ne);
if (ne) {
setSubmitError(true);
nameRef.current?.focus(); // focus first errored field
return;
}
setSubmitError(false);
// proceed with submission...
}
return (
<form onSubmit={handleSubmit} noValidate>
{submitError && (
<FormError>
<FormErrorTitle>Please fix the errors below before continuing</FormErrorTitle>
</FormError>
)}
<Field>
<FieldLabel htmlFor="name">Full name</FieldLabel>
<Input
ref={nameRef}
id="name"
aria-invalid={nameError ? 'true' : undefined}
aria-describedby={nameError ? 'name-error' : undefined}
/>
{nameError && <FieldError id="name-error">{nameError}</FieldError>}
</Field>
<button type="submit">Submit</button>
</form>
);
}API Reference
FormErrorProps
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Optional leading icon (e.g. a Phosphor WarningCircle). Rendered at the destructive color. aria-hidden — role="alert" carries the semantic weight. |
className | string | — | Additional CSS class names merged onto the container element. |
Sub-components
| Component | Element | Purpose |
|---|---|---|
FormError | <div> | Container with role="alert", destructive left-border, and tinted surface |
FormErrorTitle | <p> | Direct statement of the failure — always present |
FormErrorDescription | <p> | Optional secondary line (e.g. field count) |
Accessibility
FormErrorrenders withrole="alert"— the banner is announced immediately by screen readers when it appears.- The icon slot is
aria-hidden="true"— therole="alert"carries the semantic weight. - Focus management (required): On failed submission, call
focus()on the first invalid field so keyboard users do not have to hunt for errors. See the Usage example above. aria-invalid: Setaria-invalid="true"on each invalidInput. This changes the input's visual treatment and communicates invalidity to assistive tech independently of the visible error text.aria-describedby: Link eachFieldErrorto itsInputviaaria-describedbyso screen readers read the error message after the field label when the user navigates to the input.
Composition Rules
FormErrorappears inside the form card, above the first field group — not at page level.- It is conditionally rendered — shown only on submit failure, hidden on pristine and valid states.
- Error messages use
--text-primary(not red text) — color + left-border signal the error; copy stays readable. - The banner auto-dismisses when the form is successfully submitted — no manual close needed.