Refusal messagePro
A refusal that says what it cannot do, why in one line, and offers the closest safe alternative.
On this page
ProA request for a private address, answered with the closest safe route.
Installation
Usage
import { RefusalMessage } from "@/components/ui/chatcn/pro/refusal-message"
<RefusalMessage
title="I can't look up where a private person lives."
reason="A home address from a name can be used to turn up at someone's door."
alternative={{ label: "Draft a public reply to her review", onSelect: draftReply }}
/>It reads as part of the answer: no red, no shield, no blame. The closest safe alternative is the biggest target, a full width row that shows progress and a check once picked, so the conversation visibly carries on. Further options sit under it as quiet links. When part of the request went ahead, partial adds a two line ledger of what was done and what was skipped, so a partial refusal never hides inside a success.
Examples
States
ProPartial, compact, no alternative and long content.
Props
| Prop | Type | Notes |
|---|---|---|
title | ReactNode | What it cannot do, first person and specific |
reason | ReactNode | Why, in one line |
alternative | { label, description?, onSelect? } | The main action. Return a promise for progress |
more | RefusalAlternative[] | Further options as links |
partial | { done, skipped } | Part of the request went ahead |
policyHref, policyLabel | string | Link to the policy |
variant | "inline" | "compact" | Default inline |
closestLabel | string | Default "Closest I can do" |
Keyboard
| Key | Action |
|---|---|
| Tab | Move to the alternative and links |
| Enter or Space | Run the alternative |
Accessibility
- The alternative is one button whose name includes its label and description; progress is announced politely.
- The partial ledger prefixes Done and Skipped for screen readers.
Motion
The arrow leans 2 px forward on hover in 150 ms; press scales to 0.99. The check swaps in over 180 ms. Reduced motion keeps the arrow still.