Feedback
Four mechanisms report outcomes to the user: inline FormControl.Validation, Banner, Toast, and Dialog. Each answers a different combination of four questions: how long the message persists, whether it blocks, what it is about, and who caused it. Pick by those answers, never by how prominent the message should feel.
- Layer
- Pattern
- Rules
- 12 numbered
- Examples
- 10 do and don’t
- Updated
- Source
- feedback.md
Four mechanisms, four places
FormControl.ValidationUnder one fieldUntil the field is fixedBannerTop of a page, section or formUntil the condition clearsToastOver the app, about an action just doneTransient, about 5 secondsDialogModal, blocks the pageUntil the user decides
The decision table
Section titled “The decision table”| Mechanism | Persists | Blocks | Scope | Initiated by | Use for |
|---|---|---|---|---|---|
FormControl.Validation variant="error" | until the field is fixed | no | one field | the user’s input | a field that fails validation |
Banner | until the condition clears or the user dismisses | no | a page, section, or form | system state or a server response | form-level and server errors, quota and maintenance notices, a page-level warning |
Toast via useToast | transient, auto-dismisses (default 5s) | no | the whole app, about an action that just finished | the user’s own action | “Saved”, “Copied”, “Story published”, a background failure with a retry elsewhere |
Dialog | until the user decides | yes, modal | one decision | the user’s action, needing confirmation or input | destructive confirmations, unsaved-changes prompts, a short focused form |
- Decide with three questions in order. Is it about one field? Then
FormControl.Validation. Does the user have to decide something before continuing? ThenDialog. Otherwise: does it stay relevant while the user looks at this page? ThenBanner; if it is about an action that just completed and needs no follow-up,Toast. - A field error is always inline
FormControl.Validationon that field. It is never aBanner, never aToast, never aDialog. - A form-level or server error is a
Banner variant="danger"above the fields inside the form. It is not dismissible (onDismissis omitted) while the error still needs fixing. - A
Toastconfirms success of a completed action. It never carries information the user must act on: an error that blocks the user’s task is aBanneror aFormControl.Validation, and a decision is aDialog. A danger toast is acceptable only for a background failure that the user can recover from elsewhere. - A
Dialogis used only when the user must decide or supply something before the flow continues. A status message (“Saved”) is never a dialog. A destructive confirmation names the object in itstitle(“Delete article?”) and its confirming button (<Button variant="danger">Delete article</Button>). - One channel per event. A saved form gets a toast, or a success banner, not both. A failed request gets one banner, not a banner and a toast.
variantis chosen by meaning:successfor done,dangerfor failed or destructive,warning(Banner) for something needing attention,infofor neutral notices.Banner variant="danger"and a danger toast are announced assertively (role="alert"); the others politely (role="status"). Roles are never overridden.- A
BannergetsonDismissonly when the message is non-critical (an announcement, a tip). A dismissed critical banner cannot be recovered by the user. - Empty results are not feedback. Zero records is an
EmptyState; a filter that hides every row keeps the table and speaks throughDataTableemptyText. Neither is aBanner. See data-display.md. - Loading is not feedback about an outcome. A known layout loads behind
Skeleton; an indeterminate short wait showsSpinnerwith alabel; a measurable job showsProgressBarwitharia-label. See data-display.md. Tooltipis a hint about a control, not a feedback channel. It never reports an outcome or an error.- Feedback copy states what happened and, for errors, what to do next, in sentence case without exclamation marks:
title="Could not publish", body “The image is larger than 10 MB. Choose a smaller file.” See content.md.
Do / Don’t
Section titled “Do / Don’t”Do
<FormControl.Validation variant="error">Enter a valid email address.</FormControl.Validation>One field, inline
<Banner variant="danger" title="Could not save the story">The server rejected the request. Try again in a few minutes.</Banner>Form-level, persistent, not dismissible
toast({ variant: 'success', title: 'Story published', message: 'Readers can see it now.' })Transient confirmation of the user’s own action
<Dialog open={open} onClose={close} title="Delete article?" description="Readers will lose access immediately." footer={<><Button variant="invisible" onClick={close}>Cancel</Button><Button variant="danger" onClick={confirm}>Delete article</Button></>} />A decision that blocks
<Banner variant="info" title="Scheduled maintenance" onDismiss={hide}>Publishing pauses on Sunday from 02:00 to 03:00 UTC.</Banner>Non-critical notice, so dismissible
Don’t
toast({ variant: 'danger', message: 'Headline is required' })A field error in a toast; it disappears and is not tied to the field
<Dialog title="Saved" …>A status message as a modal
<Banner variant="danger" title="Could not save" onDismiss={hide}>While the form still cannot be saved (a dismissible blocking error)
Calling toast({ variant: 'success', … }) and rendering <Banner variant="success"> for the same save (two channels for one event)
<Banner variant="info" title="No stories yet">An empty collection is an EmptyState