{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chain-of-thought",
  "title": "Chain of Thought",
  "author": "Jamie Thompson <jamie@jamiethompson.design> (https://jamiethompson.design)",
  "description": "Collapsible step-by-step trace of an AI agent's reasoning: a header toggle (ChainOfThoughtHeader), then ChainOfThoughtStep rows with icon, label, description and complete/active/pending status, optional ChainOfThoughtSearchResults badges and a ChainOfThoughtImage with caption. Use it to show tool calls, searches and intermediate findings while an agent works; controlled or uncontrolled via open/defaultOpen/onOpenChange.",
  "dependencies": [
    "cn",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "collapsible"
  ],
  "files": [
    {
      "path": "registry/ai/chain-of-thought.tsx",
      "content": "// Derived from Vercel AI Elements chain-of-thought.tsx (Apache-2.0, Copyright 2023 Vercel, Inc.).\n// Modified for uifiles: ported from Radix UI to Base UI; dependencies point at @uifiles.\n\"use client\"\n\nimport { cn } from \"cn\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { BrainIcon, ChevronDownIcon, DotIcon } from \"lucide-react\"\nimport type { ComponentProps, ReactNode } from \"react\"\nimport {\n  Children,\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\"\nimport { Badge } from \"@/components/ui/badge\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\n\ninterface ChainOfThoughtContextValue {\n  isOpen: boolean\n  setIsOpen: (open: boolean) => void\n}\n\nconst ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(\n  null\n)\n\nconst useChainOfThought = () => {\n  const context = useContext(ChainOfThoughtContext)\n  if (!context) {\n    throw new Error(\n      \"ChainOfThought components must be used within ChainOfThought\"\n    )\n  }\n  return context\n}\n\nexport type ChainOfThoughtProps = ComponentProps<\"div\"> & {\n  open?: boolean | undefined\n  defaultOpen?: boolean | undefined\n  onOpenChange?: ((open: boolean) => void) | undefined\n}\n\nexport const ChainOfThought = memo(\n  ({\n    className,\n    open,\n    defaultOpen = false,\n    onOpenChange,\n    children,\n    ...props\n  }: ChainOfThoughtProps) => {\n    // The latest callback lives in a ref so the setter (and the context value\n    // built from it) keeps one identity across parent renders, as Radix's\n    // useControllableState did upstream.\n    const onOpenChangeRef = useRef(onOpenChange)\n    useEffect(() => {\n      onOpenChangeRef.current = onOpenChange\n    })\n    const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)\n    const isControlled = open !== undefined\n    const isOpen = isControlled ? open : uncontrolledOpen\n    const setIsOpen = useCallback(\n      (next: boolean) => {\n        if (!isControlled) setUncontrolledOpen(next)\n        onOpenChangeRef.current?.(next)\n      },\n      [isControlled]\n    )\n\n    const chainOfThoughtContext = useMemo(\n      () => ({ isOpen, setIsOpen }),\n      [isOpen, setIsOpen]\n    )\n\n    return (\n      <ChainOfThoughtContext.Provider value={chainOfThoughtContext}>\n        <Collapsible\n          className={cn(\"not-prose w-full space-y-4\", className)}\n          onOpenChange={setIsOpen}\n          open={isOpen}\n          {...props}\n        >\n          {children}\n        </Collapsible>\n      </ChainOfThoughtContext.Provider>\n    )\n  }\n)\n\nexport type ChainOfThoughtHeaderProps = ComponentProps<\n  typeof CollapsibleTrigger\n>\n\nexport const ChainOfThoughtHeader = memo(\n  ({ className, children, ...props }: ChainOfThoughtHeaderProps) => {\n    const { isOpen } = useChainOfThought()\n\n    return (\n      <CollapsibleTrigger\n        className={cn(\n          // min-h-6 lifts the 20 px text row to the WCAG 2.2 24 px target size\n          // without changing the type size or weight.\n          \"flex min-h-6 w-full items-center gap-2 text-sm text-muted-foreground transition-colors hover:text-foreground\",\n          className\n        )}\n        {...props}\n      >\n        <BrainIcon className=\"size-4\" />\n        <span className=\"flex-1 text-left\">\n          {children ?? \"Chain of Thought\"}\n        </span>\n        <ChevronDownIcon\n          className={cn(\n            \"size-4 transition-transform\",\n            isOpen ? \"rotate-180\" : \"rotate-0\"\n          )}\n        />\n      </CollapsibleTrigger>\n    )\n  }\n)\n\ntype ChainOfThoughtStepStatus = \"complete\" | \"active\" | \"pending\"\n\nexport type ChainOfThoughtStepProps = ComponentProps<\"div\"> & {\n  icon?: LucideIcon | undefined\n  label: ReactNode\n  description?: ReactNode | undefined\n  status?: ChainOfThoughtStepStatus | undefined\n}\n\n// uifiles: upstream dims pending steps with `text-muted-foreground/50`, which\n// fails WCAG AA contrast (1.96:1 on the light theme). Keep the text readable\n// and dim only the step icon instead.\nconst stepStatusStyles: Record<ChainOfThoughtStepStatus, string> = {\n  active: \"text-foreground\",\n  complete: \"text-muted-foreground\",\n  pending: \"text-muted-foreground [&>div:first-child>svg]:opacity-50\",\n}\n\nexport const ChainOfThoughtStep = memo(\n  ({\n    className,\n    icon: Icon = DotIcon,\n    label,\n    description,\n    status = \"complete\",\n    children,\n    ...props\n  }: ChainOfThoughtStepProps) => (\n    <div\n      className={cn(\n        \"flex gap-2 text-sm\",\n        // A status value this version does not know gets the neutral\n        // treatment rather than an unstyled row.\n        stepStatusStyles[status] ?? stepStatusStyles.complete,\n        \"animate-in fade-in-0 slide-in-from-top-2\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"relative mt-0.5\">\n        <Icon className=\"size-4\" />\n        <div className=\"absolute top-7 bottom-0 left-1/2 -mx-px w-px bg-border\" />\n      </div>\n      <div className=\"flex-1 space-y-2 overflow-hidden\">\n        <div>{label}</div>\n        {description && (\n          <div className=\"text-xs text-muted-foreground\">{description}</div>\n        )}\n        {children}\n      </div>\n    </div>\n  )\n)\n\nexport type ChainOfThoughtSearchResultsProps = ComponentProps<\"div\">\n\nexport const ChainOfThoughtSearchResults = memo(\n  ({ className, children, ...props }: ChainOfThoughtSearchResultsProps) => {\n    if (Children.toArray(children).length === 0) return null\n\n    return (\n      <div\n        className={cn(\"flex flex-wrap items-center gap-2\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    )\n  }\n)\n\nexport type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>\n\nexport const ChainOfThoughtSearchResult = memo(\n  ({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (\n    <Badge\n      className={cn(\"gap-1 px-2 py-0.5 text-xs font-normal\", className)}\n      variant=\"secondary\"\n      {...props}\n    >\n      {children}\n    </Badge>\n  )\n)\n\nexport type ChainOfThoughtContentProps = ComponentProps<\n  typeof CollapsibleContent\n>\n\nexport const ChainOfThoughtContent = memo(\n  ({ className, children, ...props }: ChainOfThoughtContentProps) => (\n    <CollapsibleContent\n      className={cn(\n        \"mt-2 space-y-3\",\n        \"text-popover-foreground outline-none data-open:animate-in data-open:slide-in-from-top-2 data-closed:animate-out data-closed:fade-out-0 data-closed:slide-out-to-top-2\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </CollapsibleContent>\n  )\n)\n\nexport type ChainOfThoughtImageProps = ComponentProps<\"div\"> & {\n  caption?: string | undefined\n}\n\nexport const ChainOfThoughtImage = memo(\n  ({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (\n    <div className={cn(\"mt-2 space-y-2\", className)} {...props}>\n      <div className=\"relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg bg-muted p-3\">\n        {children}\n      </div>\n      {caption && <p className=\"text-xs text-muted-foreground\">{caption}</p>}\n    </div>\n  )\n)\n\nChainOfThought.displayName = \"ChainOfThought\"\nChainOfThoughtHeader.displayName = \"ChainOfThoughtHeader\"\nChainOfThoughtStep.displayName = \"ChainOfThoughtStep\"\nChainOfThoughtSearchResults.displayName = \"ChainOfThoughtSearchResults\"\nChainOfThoughtSearchResult.displayName = \"ChainOfThoughtSearchResult\"\nChainOfThoughtContent.displayName = \"ChainOfThoughtContent\"\nChainOfThoughtImage.displayName = \"ChainOfThoughtImage\"\n",
      "type": "registry:component",
      "target": "components/ai/chain-of-thought.tsx"
    }
  ],
  "docs": "Derived from Vercel AI Elements (Apache-2.0, Copyright 2023 Vercel, Inc.); ported to Base UI and maintained by uifiles. Built for the Base UI styles (base-nova): it composes the Base UI wrappers from components/ui and relies on their render prop and event signatures, so it does not work on Radix or React Aria styles. Ported to Base UI. @radix-ui/react-use-controllable-state replaced with a useState controlled/uncontrolled pattern (open/defaultOpen/onOpenChange unchanged). Structure change: ChainOfThought now renders a single Base UI Collapsible root (still a div, same className/props) and ChainOfThoughtHeader/ChainOfThoughtContent render the Trigger/Panel directly instead of each wrapping their own Collapsible; upstream's split roots left the trigger's aria-controls pointing at a panel that never exists (axe aria-valid-attr-value). Panel animation selectors use data-open/data-closed instead of data-[state=...]. Pending steps keep text at text-muted-foreground and dim only the icon (upstream's text-muted-foreground/50 fails WCAG AA at 1.96:1). A status value ChainOfThoughtStep does not know falls back to the neutral (complete) styling. ChainOfThoughtSearchResults renders nothing when it has no children, so an empty results list adds no spacing. ChainOfThoughtImage is a frame around the image you pass as children: that element must carry its own alt text (alt=\"\" if decorative); the optional caption renders as a paragraph below it.",
  "categories": [
    "ai",
    "chat",
    "agent"
  ],
  "type": "registry:component"
}