Error

Three placements, one voice. Inline for anything the user can fix in place: the shadcn/ui Field puts the message under its control in danger, and the edge and ring follow. Toast for a background failure with a retry. A modal only when work was lost or the next step is blocked, built on Alert dialog. Say what happened and what to do next; never blame, never a code without a sentence.

<Field data-invalid>
  <FieldLabel htmlFor="email">Approver email</FieldLabel>
  <Input id="email" aria-invalid aria-describedby="email-err" />
  <FieldError id="email-err">Enter a full address, like finance@acme.com.</FieldError>
</Field>

toast.error("Couldn't reach the bank", { action: { label: "Retry", onClick: retry } });

<ErrorDialog open={failed} onOpenChange={setFailed} title="The payment didn't go through" description="…" onRetry={retry} />

Inline

One message as a line; several as a list, duplicates collapsed. The label stays in ink.

Used for the dashboard only; agents never see it.

Form summary

When more than one field fails on submit: a danger Alert at the top linking to each field, plus the inline messages.

Modal

Alert dialog on a danger tile. Retry is primary when the action can be repeated; a details block carries the reference.

Toast

Sonner's error recipe: danger-soft on a danger edge, five seconds, Retry as the action.