{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"refusal-message","type":"registry:ui","title":"Refusal Message","description":"How to say no without ending the conversation — the refusal, a plain-language reason, and one redirect the reader can click.","author":"Scrim UI (https://scrimui.dev)","categories":["safety"],"docs":"https://scrimui.dev/components/refusal-message","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/refusal-message/refusal-message.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Say no like a colleague, not like a firewall.\n *\n * A bare \"I can't help with that\" is a dead end: the user learns nothing and\n * the conversation is over. A useful refusal does three things, in order —\n * states what won't be done, says why in the user's terms (never a policy\n * clause number, which reads as hiding behind a rulebook), and offers the one\n * pivot that is still on the table.\n *\n * **The redirect is the component.** `suggestion` is not decoration; it is\n * the difference between a refusal and a rejection. When it is present it is\n * a button, because making the user retype the safe version of their own\n * request is friction added at exactly the moment they are most frustrated.\n *\n * **A refusal is not an error.** No red, no alert triangle — the system\n * worked as intended. The surface stays neutral so the reader's eye goes to\n * the words, not to alarm styling the situation has not earned.\n *\n * **Keep it in the thread.** The refusal is an assistant turn like any other;\n * pulling it out into a modal or a banner breaks the conversation's record of\n * what was asked and declined.\n */\n\nexport type RefusalMessageProps = {\n  /** The refusal itself, in one or two plain sentences. */\n  message: string;\n  /** The plain-language basis for the refusal — \"why\", not a policy citation. */\n  reason?: string;\n  /** The offered pivot. Rendered as an action, not a hint. */\n  suggestion?: string;\n  /** Fired when the reader accepts the pivot — typically sends it as a new prompt. */\n  onSuggestion?: () => void;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Icons                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction ShieldIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"16\" height=\"16\">\n      <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" />\n    </svg>\n  );\n}\n\nfunction ArrowRightIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"13\" height=\"13\">\n      <path d=\"M5 12h14\" />\n      <path d=\"m12 5 7 7-7 7\" />\n    </svg>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* RefusalMessage                                                      */\n/* ------------------------------------------------------------------ */\n\nexport function RefusalMessage({\n  message,\n  reason,\n  suggestion,\n  onSuggestion,\n  className = \"\",\n}: RefusalMessageProps) {\n  return (\n    <div className={`flex items-start gap-3 rounded-2xl rounded-tl-md border border-zinc-200 bg-zinc-50 px-4 py-3 dark:border-zinc-800 dark:bg-zinc-900 ${className}`}>\n      <span className=\"mt-0.5 shrink-0 text-zinc-500 dark:text-zinc-400\">\n        <ShieldIcon />\n      </span>\n      <div className=\"min-w-0 flex-1\">\n        <p className=\"text-sm leading-6 text-zinc-900 dark:text-zinc-100\">{message}</p>\n\n        {/* zinc-600 on the caption: it sits on the zinc-50 surface, where\n            zinc-500 measures under AA at 12px. */}\n        {reason && (\n          <p className=\"mt-1.5 text-[13px] leading-5 text-zinc-600 dark:text-zinc-400\">\n            <span className=\"font-medium\">Why: </span>\n            {reason}\n          </p>\n        )}\n\n        {suggestion && (\n          <button\n            type=\"button\"\n            onClick={onSuggestion}\n            className=\"mt-2.5 inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-2.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800\"\n          >\n            <span className=\"truncate\">{suggestion}</span>\n            <span className=\"shrink-0\">\n              <ArrowRightIcon />\n            </span>\n          </button>\n        )}\n      </div>\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/refusal-message.tsx"}]}