{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool-approval",
  "type": "registry:component",
  "title": "ToolApproval",
  "description": "Inline approve or deny card for an agent action, with risk, a preview of what will run, and keyboard shortcuts.",
  "dependencies": [
    "@base-ui/react@^1.9.0",
    "lucide-react@^1.47.0"
  ],
  "registryDependencies": [
    "https://control-ui.dev/r/badge.json",
    "https://control-ui.dev/r/button.json",
    "https://control-ui.dev/r/core.json",
    "https://control-ui.dev/r/kbd.json",
    "https://control-ui.dev/r/live-status.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/tool-approval-knobs.ts",
      "target": "@components/control-ui/knob-contracts/tool-approval-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/tool-approval.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const toolApprovalKnobs = [\n  \"--cui-tool-approval-background\",\n  \"--cui-tool-approval-foreground\",\n  \"--cui-tool-approval-border-color\",\n  \"--cui-tool-approval-border-width\",\n  \"--cui-tool-approval-radius\",\n  \"--cui-tool-approval-shadow\",\n  \"--cui-tool-approval-padding\",\n  \"--cui-tool-approval-body-background\",\n  \"--cui-tool-approval-body-border-color\",\n  \"--cui-tool-approval-body-border-width\",\n  \"--cui-tool-approval-body-radius\",\n  \"--cui-tool-approval-body-shadow\",\n  \"--cui-tool-approval-body-padding\",\n  \"--cui-tool-approval-body-gap\",\n  \"--cui-tool-approval-footer-border-color\",\n  \"--cui-tool-approval-footer-border-width\",\n  \"--cui-tool-approval-footer-padding\",\n  \"--cui-tool-approval-footer-gap\",\n  \"--cui-tool-approval-preview-background\",\n  \"--cui-tool-approval-preview-border-color\",\n  \"--cui-tool-approval-preview-border-width\",\n  \"--cui-tool-approval-preview-radius\",\n  \"--cui-tool-approval-preview-padding\",\n  \"--cui-tool-approval-preview-gap\",\n  \"--cui-tool-approval-label-foreground\",\n  \"--cui-tool-approval-description-foreground\",\n  \"--cui-tool-approval-status-foreground\",\n] as const;\nexport type ToolApprovalKnobStyle = Partial<Record<(typeof toolApprovalKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/tool-approval.css",
      "target": "@components/control-ui/styles/recipes/tool-approval.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"tool-approval\"][data-slot=\"root\"]) {\n    --cui-tool-approval-background: var(--card);\n    --cui-tool-approval-foreground: var(--card-foreground);\n    --cui-tool-approval-border-color: var(--border);\n    --cui-tool-approval-border-width: var(--control-rim-width);\n    --cui-tool-approval-radius: var(--radius-lg);\n    --cui-tool-approval-shadow: var(--shadow-sm);\n    --cui-tool-approval-padding: 0px;\n    --cui-tool-approval-body-background: transparent;\n    --cui-tool-approval-body-border-color: transparent;\n    --cui-tool-approval-body-border-width: 0px;\n    --cui-tool-approval-body-radius: var(--radius-lg);\n    --cui-tool-approval-body-shadow: 0 0 transparent;\n    --cui-tool-approval-body-padding: calc(var(--spacing) * 3);\n    --cui-tool-approval-body-gap: calc(var(--spacing) * 3);\n    --cui-tool-approval-footer-border-color: var(--border);\n    --cui-tool-approval-footer-border-width: var(--control-rim-width);\n    --cui-tool-approval-footer-padding: calc(var(--spacing) * 2.5);\n    --cui-tool-approval-footer-gap: calc(var(--spacing) * 2);\n    --cui-tool-approval-preview-background: transparent;\n    --cui-tool-approval-preview-border-color: transparent;\n    --cui-tool-approval-preview-border-width: 0px;\n    --cui-tool-approval-preview-radius: var(--radius-md);\n    --cui-tool-approval-preview-padding: 0px;\n    --cui-tool-approval-preview-gap: calc(var(--spacing) * 1.5);\n    --cui-tool-approval-label-foreground: var(--muted-foreground);\n    --cui-tool-approval-description-foreground: var(--muted-foreground);\n    --cui-tool-approval-status-foreground: var(--muted-foreground);\n    display: grid;\n    padding: var(--cui-tool-approval-padding);\n    border-radius: var(--cui-tool-approval-radius);\n    background: var(--cui-tool-approval-background);\n    color: var(--cui-tool-approval-foreground);\n    box-shadow:\n      inset 0 0 0 var(--cui-tool-approval-border-width) var(--cui-tool-approval-border-color),\n      var(--cui-tool-approval-shadow);\n    font-size: var(--text-body);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"body\"]) {\n    display: grid;\n    gap: var(--cui-tool-approval-body-gap);\n    padding: var(--cui-tool-approval-body-padding);\n    border-radius: var(--cui-tool-approval-body-radius);\n    background: var(--cui-tool-approval-body-background);\n    box-shadow:\n      inset 0 0 0 var(--cui-tool-approval-body-border-width) var(--cui-tool-approval-body-border-color),\n      var(--cui-tool-approval-body-shadow);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"footer\"]) {\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n    gap: var(--cui-tool-approval-footer-gap);\n    padding: var(--cui-tool-approval-footer-padding);\n    border-block-start: var(--cui-tool-approval-footer-border-width) solid var(--cui-tool-approval-footer-border-color);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"preview\"]) {\n    display: grid;\n    grid-template-columns: auto minmax(0, 1fr);\n    column-gap: calc(var(--spacing) * 4);\n    row-gap: var(--cui-tool-approval-preview-gap);\n    padding: var(--cui-tool-approval-preview-padding);\n    border-radius: var(--cui-tool-approval-preview-radius);\n    background: var(--cui-tool-approval-preview-background);\n    box-shadow: inset 0 0 0 var(--cui-tool-approval-preview-border-width) var(--cui-tool-approval-preview-border-color);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"field\"]) {\n    display: contents;\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"field-label\"]) {\n    color: var(--cui-tool-approval-label-foreground);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"description\"]) {\n    color: var(--cui-tool-approval-description-foreground);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"status\"]) {\n    display: inline-flex;\n    align-items: center;\n    gap: var(--spacing);\n    color: var(--cui-tool-approval-status-foreground);\n    transition-property: opacity;\n    transition-duration: var(--duration-base);\n    transition-timing-function: var(--ease-emphasized);\n  }\n\n  @starting-style {\n    :where([data-control-family=\"tool-approval\"][data-slot=\"status\"]) {\n      opacity: 0;\n    }\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"root\"][data-state=\"approved\"] [data-slot=\"status-icon\"]) {\n    color: var(--success-text);\n  }\n\n  :where([data-control-family=\"tool-approval\"][data-slot=\"root\"][data-state=\"denied\"] [data-slot=\"status-icon\"]) {\n    color: var(--destructive-text);\n  }\n}\n\n@property --cui-tool-approval-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-border-width {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-tool-approval-padding {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-body-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-body-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-body-border-width {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-body-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-body-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-tool-approval-body-padding {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-body-gap {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-footer-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-footer-border-width {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-footer-padding {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-footer-gap {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-preview-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-preview-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-preview-border-width {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-preview-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-preview-padding {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-preview-gap {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-tool-approval-label-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-description-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-tool-approval-status-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/tool-approval.tsx",
      "target": "@components/control-ui/tool-approval.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { Check, ShieldAlert, X } from \"lucide-react\";\nimport type { ComponentProps, CSSProperties, KeyboardEvent, MouseEvent, ReactNode } from \"react\";\nimport { createContext, useContext, useEffect, useId, useRef } from \"react\";\n\nimport type { RenderProp } from \"@/components/control-ui/control-props\";\nimport type { ToolApprovalKnobStyle } from \"@/components/control-ui/knob-contracts/tool-approval-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport type { BadgeColor } from \"@/components/control-ui/ui/badge\";\nimport { Badge } from \"@/components/control-ui/ui/badge\";\nimport { Button } from \"@/components/control-ui/ui/button\";\nimport { Kbd } from \"@/components/control-ui/ui/kbd\";\nimport { LiveStatus } from \"@/components/control-ui/ui/live-status\";\n\nexport type ToolApprovalState = \"pending\" | \"approved\" | \"denied\";\n\nexport type ToolApprovalRiskLevel = \"low\" | \"medium\" | \"high\";\n\ntype ToolApprovalStyleProps<Props> = Omit<Props, \"style\"> & { style?: CSSProperties & ToolApprovalKnobStyle };\n\nexport type ToolApprovalProps = ToolApprovalStyleProps<ComponentProps<\"section\">> & {\n  state?: ToolApprovalState;\n  risk?: ToolApprovalRiskLevel;\n  onApprove?: () => void;\n  onDeny?: () => void;\n  autoFocus?: boolean;\n};\n\ntype ToolApprovalContextValue = {\n  state: ToolApprovalState;\n  risk: ToolApprovalRiskLevel;\n  titleId: string;\n  approve: () => void;\n  deny: () => void;\n};\n\nconst ToolApprovalContext = createContext<ToolApprovalContextValue | null>(null);\n\nfunction useToolApprovalContext() {\n  const context = useContext(ToolApprovalContext);\n  if (!context) throw new Error(\"ToolApproval compound components must be rendered inside <ToolApproval>.\");\n  return context;\n}\n\nconst decisionAnnouncements = {\n  pending: \"\",\n  approved: \"Action approved\",\n  denied: \"Action denied\",\n} satisfies Record<ToolApprovalState, string>;\n\nexport function ToolApproval({\n  state = \"pending\",\n  risk = \"medium\",\n  onApprove,\n  onDeny,\n  autoFocus = false,\n  className,\n  children,\n  onKeyDown,\n  ...props\n}: ToolApprovalProps) {\n  const titleId = useId();\n  const rootRef = useRef<HTMLElement>(null);\n  const isPending = state === \"pending\";\n\n  const approve = () => {\n    if (isPending) onApprove?.();\n  };\n  const deny = () => {\n    if (isPending) onDeny?.();\n  };\n\n  // native autofocus only fires at document load, and this card mounts mid-run\n  useEffect(() => {\n    if (autoFocus) rootRef.current?.focus();\n  }, [autoFocus]);\n\n  function handleKeyDown(event: KeyboardEvent<HTMLElement>) {\n    onKeyDown?.(event);\n    if (event.defaultPrevented || !isPending || event.nativeEvent.isComposing) return;\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      deny();\n      return;\n    }\n    if (event.key === \"Enter\" && (event.metaKey || event.ctrlKey)) {\n      event.preventDefault();\n      approve();\n    }\n  }\n\n  return (\n    <ToolApprovalContext.Provider value={{ state, risk, titleId, approve, deny }}>\n      <section\n        ref={rootRef}\n        data-control-ui=\"tool-approval\"\n        data-control-family=\"tool-approval\"\n        data-slot=\"root\"\n        data-state={state}\n        data-risk={risk}\n        aria-labelledby={titleId}\n        tabIndex={-1}\n        onKeyDown={handleKeyDown}\n        className={cn(\"relative w-full min-w-0\", className)}\n        {...props}\n      >\n        {children}\n        <LiveStatus message={decisionAnnouncements[state]} />\n      </section>\n    </ToolApprovalContext.Provider>\n  );\n}\n\nexport type ToolApprovalBodyProps = ToolApprovalStyleProps<ComponentProps<\"div\">>;\n\nexport function ToolApprovalBody({ className, ...props }: ToolApprovalBodyProps) {\n  return (\n    <div\n      data-control-ui=\"tool-approval\"\n      data-control-family=\"tool-approval\"\n      data-slot=\"body\"\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type ToolApprovalHeaderProps = ToolApprovalStyleProps<ComponentProps<\"div\">>;\n\nexport function ToolApprovalHeader({ className, ...props }: ToolApprovalHeaderProps) {\n  return (\n    <div\n      data-control-ui=\"tool-approval\"\n      data-control-family=\"tool-approval\"\n      data-slot=\"header\"\n      className={cn(\"flex items-start gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type ToolApprovalIconProps = ToolApprovalStyleProps<ComponentProps<\"span\">>;\n\nexport function ToolApprovalIcon({ className, children, ...props }: ToolApprovalIconProps) {\n  return (\n    <span\n      data-control-ui=\"tool-approval\"\n      data-control-family=\"tool-approval\"\n      data-slot=\"icon\"\n      aria-hidden=\"true\"\n      className={cn(\"inline-flex size-5 shrink-0 items-center justify-center [&_svg]:size-4\", className)}\n      {...props}\n    >\n      {children ?? <ShieldAlert />}\n    </span>\n  );\n}\n\nexport type ToolApprovalTitleProps = ToolApprovalStyleProps<ComponentProps<\"div\">> & {\n  render?: RenderProp<ComponentProps<\"div\">>;\n};\n\nexport function ToolApprovalTitle({ render, className, ...props }: ToolApprovalTitleProps) {\n  const { titleId } = useToolApprovalContext();\n  return useRender({\n    defaultTagName: \"div\",\n    render,\n    props: {\n      ...props,\n      id: titleId,\n      \"data-control-ui\": \"tool-approval\",\n      \"data-control-family\": \"tool-approval\",\n      \"data-slot\": \"title\",\n      className: cn(\"min-w-0 flex-1 font-medium\", className),\n    },\n  });\n}\n\nconst riskColors = {\n  low: \"neutral\",\n  medium: \"yellow\",\n  high: \"red\",\n} satisfies Record<ToolApprovalRiskLevel, BadgeColor>;\n\nconst riskLabels = {\n  low: \"Low risk\",\n  medium: \"Medium risk\",\n  high: \"High risk\",\n} satisfies Record<ToolApprovalRiskLevel, string>;\n\nexport type ToolApprovalRiskProps = Omit<ComponentProps<typeof Badge>, \"color\">;\n\nexport function ToolApprovalRisk({ children, ...props }: ToolApprovalRiskProps) {\n  const { state, risk } = useToolApprovalContext();\n  if (state !== \"pending\") return null;\n  return (\n    <Badge data-control-ui=\"tool-approval\" data-slot=\"risk\" size=\"sm\" color={riskColors[risk]} {...props}>\n      {children ?? riskLabels[risk]}\n    </Badge>\n  );\n}\n\nconst decisionLabels = {\n  approved: \"Approved\",\n  denied: \"Denied\",\n} satisfies Record<Exclude<ToolApprovalState, \"pending\">, string>;\n\nexport type ToolApprovalStatusProps = ToolApprovalStyleProps<ComponentProps<\"div\">>;\n\nexport function ToolApprovalStatus({ className, children, ...props }: ToolApprovalStatusProps) {\n  const { state } = useToolApprovalContext();\n  if (state === \"pending\") return null;\n  const StatusIcon = state === \"approved\" ? Check : X;\n  return (\n    <div\n      data-control-ui=\"tool-approval\"\n      data-control-family=\"tool-approval\"\n      data-slot=\"status\"\n      className={cn(\"shrink-0\", className)}\n      {...props}\n    >\n      <span data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"status-icon\" aria-hidden=\"true\">\n        <StatusIcon className=\"size-3.5\" />\n      </span>\n      {children ?? decisionLabels[state]}\n    </div>\n  );\n}\n\nexport type ToolApprovalDescriptionProps = ToolApprovalStyleProps<ComponentProps<\"p\">>;\n\nexport function ToolApprovalDescription({ className, ...props }: ToolApprovalDescriptionProps) {\n  const { state } = useToolApprovalContext();\n  if (state !== \"pending\") return null;\n  return <p data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"description\" className={className} {...props} />;\n}\n\nexport type ToolApprovalPreviewProps = ToolApprovalStyleProps<ComponentProps<\"dl\">>;\n\nexport function ToolApprovalPreview({ className, ...props }: ToolApprovalPreviewProps) {\n  const { state } = useToolApprovalContext();\n  if (state !== \"pending\") return null;\n  return (\n    <dl\n      data-control-ui=\"tool-approval\"\n      data-control-family=\"tool-approval\"\n      data-slot=\"preview\"\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type ToolApprovalFieldProps = Omit<ToolApprovalStyleProps<ComponentProps<\"div\">>, \"children\"> & {\n  label: ReactNode;\n  children: ReactNode;\n};\n\nexport function ToolApprovalField({ label, children, className, ...props }: ToolApprovalFieldProps) {\n  return (\n    <div data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"field\" className={className} {...props}>\n      <dt data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"field-label\">\n        {label}\n      </dt>\n      <dd data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"field-value\" className=\"min-w-0 break-words\">\n        {children}\n      </dd>\n    </div>\n  );\n}\n\nexport type ToolApprovalFooterProps = ToolApprovalStyleProps<ComponentProps<\"div\">>;\n\nexport function ToolApprovalFooter({ className, ...props }: ToolApprovalFooterProps) {\n  const { state } = useToolApprovalContext();\n  if (state !== \"pending\") return null;\n  return <div data-control-ui=\"tool-approval\" data-control-family=\"tool-approval\" data-slot=\"footer\" className={className} {...props} />;\n}\n\nexport type ToolApprovalDenyProps = ComponentProps<typeof Button>;\n\nexport function ToolApprovalDeny({ children, onClick, ...props }: ToolApprovalDenyProps) {\n  const { deny } = useToolApprovalContext();\n\n  function handleClick(event: MouseEvent<HTMLButtonElement>) {\n    onClick?.(event);\n    if (!event.defaultPrevented) deny();\n  }\n\n  return (\n    <Button\n      data-control-ui=\"tool-approval\"\n      data-slot=\"deny\"\n      type=\"button\"\n      variant=\"quiet\"\n      size=\"xs\"\n      aria-keyshortcuts=\"Escape\"\n      onClick={handleClick}\n      {...props}\n    >\n      {children ?? (\n        <>\n          Deny\n          <Kbd aria-hidden=\"true\">Esc</Kbd>\n        </>\n      )}\n    </Button>\n  );\n}\n\nexport type ToolApprovalApproveProps = ComponentProps<typeof Button>;\n\nexport function ToolApprovalApprove({ children, onClick, ...props }: ToolApprovalApproveProps) {\n  const { approve } = useToolApprovalContext();\n\n  function handleClick(event: MouseEvent<HTMLButtonElement>) {\n    onClick?.(event);\n    if (!event.defaultPrevented) approve();\n  }\n\n  return (\n    <Button\n      data-control-ui=\"tool-approval\"\n      data-slot=\"approve\"\n      type=\"button\"\n      variant=\"solid\"\n      tone=\"primary\"\n      size=\"xs\"\n      aria-keyshortcuts=\"Meta+Enter Control+Enter\"\n      onClick={handleClick}\n      {...props}\n    >\n      {children ?? (\n        <>\n          Approve\n          <Kbd aria-hidden=\"true\">⌘⏎</Kbd>\n        </>\n      )}\n    </Button>\n  );\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/tool-approval.css\"": {}
  }
}
