Skip to content
Sign in
SafetyRefusal messageMenu

Refusal messagePro

A refusal that says what it cannot do, why in one line, and offers the closest safe alternative.

ProA request for a private address, answered with the closest safe route.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/refusal-message

Usage

tsx
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

PropTypeNotes
titleReactNodeWhat it cannot do, first person and specific
reasonReactNodeWhy, in one line
alternative{ label, description?, onSelect? }The main action. Return a promise for progress
moreRefusalAlternative[]Further options as links
partial{ done, skipped }Part of the request went ahead
policyHref, policyLabelstringLink to the policy
variant"inline" | "compact"Default inline
closestLabelstringDefault "Closest I can do"

Keyboard

KeyAction
TabMove to the alternative and links
Enter or SpaceRun 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.