Message error

What to show in place of a reply that failed, hit a usage limit or was stopped partway.

Loading preview…

Install

  1. 1

    Run this command in your Tailwind CSS project. It adds components/ai/message-error-error.tsx and installs its dependencies.

    $ npx shadcn@latest add https://gradientdeck.com/ai-components/r/message-error-error.json

Usage & API

Example

message-error-error.tsx
import { MessageError } from "@/components/ai/message-error-error"

<MessageError
  onRetry={() => {}}
/>

Props MessageError

PropType & descriptionRequired
titlestringOptional
detailstringOptional
onRetry() => voidRequired
retryingbooleanOptional

Dependencies

  • React
  • Tailwind CSS
  • motion
  • lucide-react
Implementation notes

Failed reply is MessageError. The icon is a triangle in a red tile. title and detail are paragraphs, so you can replace the defaults without restyling. The demo's title is Connection lost and the detail matches the default about the message being saved. Retry is disabled while retrying is true, and the label becomes Retrying. The spin is a linear 0.9 second loop. The alert enters with a small rise in the demo's wrapper, and it leaves when the state becomes ok. That enter and exit are the demo's AnimatePresence, not props. Pass a new title for a safety block or a timeout if the default something-went-wrong is too vague. The partial reply above the alert is ordinary text. The component does not truncate it.

Choosing a layout

Use Failed reply when the stream died and a retry might work. Leave the partial text above it, the way the demo leaves the wedding toast unfinished, so the person can see that something was said. Use Usage limit when the API refused because the plan is exhausted. The countdown is the reason to wait, and Keep going with the fallback model is the way to stay in the thread without upgrading. The upgrade button always says Upgrade to Pro, even though the plan name in the heading is the plan prop. Use Stopped when the person hit stop and the rest of the answer can still be requested. Streaming text's caret controls include their own Stopped status, Continue and Skip to end. Use this notice when you do not already show that toolbar.

Text shimmer should unmount when you show any of these. Leaving Thinking above a red alert looks like the request is both running and dead. Message actions can stay if there is partial text worth copying, but Regenerate and Retry will confuse people if both are visible. Pick one. Branch picker is irrelevant until you have more than one finished version. The limit card is a status, not an alert, so it does not steal focus. The failure is an alert and will be announced.

Retry spins and the old error is still on screen.

retrying only changes the button. It does not hide the alert, and it does not clear title or detail. Hide the component when your retry succeeds, the way the demo moves from retrying to ok and completes the truncated sentence. If the retry fails, set retrying back to false so the label returns to Retry. The alert stays announced as an alert for as long as it is mounted, so do not leave a resolved failure in the tree.

The limit card says Upgrade to Pro while the heading names a different plan.

plan is interpolated only into You've hit the plan limit. It defaults to Free. The primary button is fixed copy, Upgrade to Pro, and it always calls onUpgrade. The secondary button is Keep going with plus fallbackModel, and it is omitted unless you pass both fallbackModel and onSwitchModel. The countdown is empty of numbers until mount, on purpose, and then ticks once a second from resetsAt. It will not hit zero and enable the button. Nothing here enables sending. Your composer has to do that when the time passes or the model changes.