{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "author": "Jamie Thompson <jamie@jamiethompson.design> (https://jamiethompson.design)",
  "description": "Syntax-highlighted code block for AI chat and agent UIs, rendered with shiki (github-light-high-contrast/github-dark-high-contrast, lazy-loaded per language, token cache, one highlight per snippet). Composes a header with filename/title, a copy-to-clipboard button with a checkmark timeout, a language selector built on Select, optional line numbers, and a body that shows raw text instantly and swaps in highlighted tokens when the highlighter resolves; a block that overflows becomes a keyboard-focusable, labeled scroll container. Use it for model-generated code, tool inputs/outputs, and snippets inside Response or Tool; use CodeBlockContent/CodeBlockContainer to build custom layouts.",
  "dependencies": [
    "cn",
    "lucide-react",
    "shiki@^4.4"
  ],
  "registryDependencies": [
    "button",
    "select"
  ],
  "files": [
    {
      "path": "registry/ai/code-block.tsx",
      "content": "// Derived from Vercel AI Elements code-block.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 { CheckIcon, CopyIcon } from \"lucide-react\"\nimport type { ComponentProps, CSSProperties, HTMLAttributes } from \"react\"\nimport {\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from \"react\"\nimport type {\n  BundledLanguage,\n  BundledTheme,\n  HighlighterGeneric,\n  SpecialLanguage,\n  ThemedToken,\n} from \"shiki\"\nimport {\n  bundledLanguages,\n  bundledLanguagesInfo,\n  createHighlighter,\n} from \"shiki\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\n\n// Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline\nconst isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1\nconst isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2\nconst isUnderline = (fontStyle: number | undefined) =>\n  fontStyle && fontStyle & 4\n\n// Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint\ninterface KeyedToken {\n  token: ThemedToken\n  key: string\n}\ninterface KeyedLine {\n  tokens: KeyedToken[]\n  key: string\n}\n\nconst addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>\n  lines.map((line, lineIdx) => ({\n    key: `line-${lineIdx}`,\n    tokens: line.map((token, tokenIdx) => ({\n      key: `line-${lineIdx}-${tokenIdx}`,\n      token,\n    })),\n  }))\n\n// Token rendering component\nconst TokenSpan = ({ token }: { token: ThemedToken }) => (\n  <span\n    className=\"dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]\"\n    style={\n      {\n        backgroundColor: token.bgColor,\n        color: token.color,\n        fontStyle: isItalic(token.fontStyle) ? \"italic\" : undefined,\n        fontWeight: isBold(token.fontStyle) ? \"bold\" : undefined,\n        textDecoration: isUnderline(token.fontStyle) ? \"underline\" : undefined,\n        ...token.htmlStyle,\n      } as CSSProperties\n    }\n  >\n    {token.content}\n  </span>\n)\n\n// Line number styles using CSS counters. The gutter is `--line-digits`\n// characters wide (set on <code> from the line count) so four-digit numbers\n// stay right-aligned instead of overflowing a fixed box.\nconst LINE_NUMBER_CLASSES = cn(\n  \"block\",\n  \"before:content-[counter(line)]\",\n  \"before:inline-block\",\n  \"before:[counter-increment:line]\",\n  \"before:w-[calc(var(--line-digits,2)*1ch)]\",\n  \"before:mr-4\",\n  \"before:text-right\",\n  \"before:text-muted-foreground\",\n  \"before:font-mono\",\n  \"before:select-none\"\n)\n\n// Line rendering component\nconst LineSpan = ({\n  keyedLine,\n  showLineNumbers,\n}: {\n  keyedLine: KeyedLine\n  showLineNumbers: boolean\n}) => (\n  <span className={showLineNumbers ? LINE_NUMBER_CLASSES : \"block\"}>\n    {keyedLine.tokens.length === 0\n      ? \"\\n\"\n      : keyedLine.tokens.map(({ token, key }) => (\n          <TokenSpan key={key} token={token} />\n        ))}\n  </span>\n)\n\n// Types\ntype CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  code: string\n  language: BundledLanguage\n  showLineNumbers?: boolean\n}\n\ninterface TokenizedCode {\n  tokens: ThemedToken[][]\n  fg: string\n  bg: string\n  /** Theme custom properties (`--shiki-dark`, `--shiki-dark-bg`) for the `<pre>`. */\n  vars: Record<string, string>\n}\n\ninterface CodeBlockContextType {\n  code: string\n}\n\n// Context\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  code: \"\",\n})\n\ntype Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>\ntype HighlightLanguage = BundledLanguage | SpecialLanguage\n\nconst THEMES = {\n  dark: \"github-dark-high-contrast\",\n  light: \"github-light-high-contrast\",\n} as const\n\nconst FALLBACK_LANGUAGE: SpecialLanguage = \"text\"\nconst SPECIAL_LANGUAGES = new Set<string>([\n  \"text\",\n  \"plaintext\",\n  \"txt\",\n  \"plain\",\n  \"ansi\",\n])\n\n// One highlighter for every block, created on first use with both themes and\n// no grammars: Shiki warns from its tenth instance on, and every instance\n// would load the themes again (uifiles change; upstream made one per language).\nlet highlighterPromise: Promise<Highlighter> | undefined\n\n// Grammar loads per language, shared by concurrent callers\nconst languageLoads = new Map<string, Promise<Highlighter>>()\n\n// Token cache, keyed on the whole code and the language\nconst tokensCache = new Map<string, TokenizedCode>()\n\n// In-flight highlights per cache key, so concurrent callers share one job\nconst pending = new Map<string, Promise<void>>()\n\n// Subscribers for async token updates\nconst subscribers = new Map<string, Set<(result: TokenizedCode) => void>>()\n\n// Languages already reported as unknown (one warning each, not one per render)\nconst warnedLanguages = new Set<string>()\n\n// Callers such as Tool pass `code` straight from streamed model output, which\n// can be undefined before the first chunk; never let that reach `split`.\nconst toCodeString = (code: unknown): string =>\n  typeof code === \"string\" ? code : \"\"\n\n// NUL cannot appear in a fence info string, so \"foo:bar\" + \"baz\" and\n// \"foo\" + \"bar:baz\" get different keys.\nconst getTokensCacheKey = (code: string, language: string) =>\n  `${language}\\0${code}`\n\n// Display names for the scroll container's default label (\"TypeScript code\").\nconst languageNames = new Map<string, string>()\nfor (const info of bundledLanguagesInfo) {\n  for (const key of [info.id, ...(info.aliases ?? [])]) {\n    languageNames.set(key, info.name)\n  }\n}\n\nconst defaultContentLabel = (language: unknown): string => {\n  const name =\n    typeof language === \"string\" ? languageNames.get(language) : undefined\n  return name ? `${name} code` : \"Code\"\n}\n\n// Unknown grammars (typos, exotic fence info strings) render as plain text\n// instead of leaving a rejected highlighter in the cache. `Object.hasOwn`,\n// not `in`: \"constructor\" or \"toString\" is not a grammar either.\nconst resolveLanguage = (language: unknown): HighlightLanguage => {\n  if (typeof language !== \"string\" || language === \"\") {\n    return FALLBACK_LANGUAGE\n  }\n  if (\n    Object.hasOwn(bundledLanguages, language) ||\n    SPECIAL_LANGUAGES.has(language)\n  ) {\n    return language as HighlightLanguage\n  }\n  if (!warnedLanguages.has(language)) {\n    warnedLanguages.add(language)\n    console.warn(\n      `CodeBlock: shiki has no \"${language}\" grammar; rendering it as plain text.`\n    )\n  }\n  return FALLBACK_LANGUAGE\n}\n\nconst getSharedHighlighter = (): Promise<Highlighter> => {\n  if (highlighterPromise) {\n    return highlighterPromise\n  }\n  highlighterPromise = createHighlighter({\n    langs: [],\n    themes: [THEMES.light, THEMES.dark],\n  }).catch((error: unknown) => {\n    // A failed start (offline, chunk error) must not poison later attempts.\n    highlighterPromise = undefined\n    throw error\n  })\n  return highlighterPromise\n}\n\nconst getHighlighter = (language: HighlightLanguage): Promise<Highlighter> => {\n  const cached = languageLoads.get(language)\n  if (cached) {\n    return cached\n  }\n\n  const loaded = getSharedHighlighter()\n    .then(async (highlighter) => {\n      // Plain-text languages load nothing, and Shiki skips a grammar it\n      // already has under another alias (\"ts\" after \"typescript\").\n      await highlighter.loadLanguage(language)\n      return highlighter\n    })\n    .catch((error: unknown) => {\n      // A failed grammar load must not poison later attempts either.\n      languageLoads.delete(language)\n      throw error\n    })\n\n  languageLoads.set(language, loaded)\n  return loaded\n}\n\n// Shiki packs the dual-theme colors as \"#fff;--shiki-dark-bg:#0a0c10\";\n// React needs the color and the custom properties as separate style keys.\nconst splitThemeStyle = (\n  value: string | undefined,\n  fallback: string\n): { color: string; vars: Record<string, string> } => {\n  const [color = \"\", ...declarations] = (value ?? \"\").split(\";\")\n  const vars: Record<string, string> = {}\n  for (const declaration of declarations) {\n    const separator = declaration.indexOf(\":\")\n    if (separator > 0) {\n      vars[declaration.slice(0, separator).trim()] = declaration\n        .slice(separator + 1)\n        .trim()\n    }\n  }\n  return { color: color.trim() || fallback, vars }\n}\n\nconst tokenize = async (\n  code: string,\n  language: unknown\n): Promise<TokenizedCode> => {\n  const lang = resolveLanguage(language)\n  const highlighter = await getHighlighter(lang)\n  const result = highlighter.codeToTokens(code, { lang, themes: THEMES })\n  const bg = splitThemeStyle(result.bg, \"transparent\")\n  const fg = splitThemeStyle(result.fg, \"inherit\")\n  return {\n    bg: bg.color,\n    fg: fg.color,\n    tokens: result.tokens,\n    vars: { ...bg.vars, ...fg.vars },\n  }\n}\n\n// Create raw tokens for immediate display while highlighting loads. Shiki\n// splits on CRLF too, so the raw and highlighted line counts match.\nconst createRawTokens = (code: unknown): TokenizedCode => ({\n  bg: \"transparent\",\n  fg: \"inherit\",\n  tokens: toCodeString(code)\n    .split(/\\r?\\n/)\n    .map((line) =>\n      line === \"\"\n        ? []\n        : [\n            {\n              color: \"inherit\",\n              content: line,\n            } as ThemedToken,\n          ]\n    ),\n  vars: {},\n})\n\nconst subscribeToNothing = () => () => {}\n\n// Synchronous highlight with callback for async results. The callback runs\n// exactly once per call: synchronously on a cache hit, otherwise when the\n// shared highlight for this code and language resolves.\nexport const highlightCode = (\n  code: string,\n  language: BundledLanguage,\n  callback?: (result: TokenizedCode) => void\n): TokenizedCode | null => {\n  const text = toCodeString(code)\n  const tokensCacheKey = getTokensCacheKey(text, String(language))\n\n  const cached = tokensCache.get(tokensCacheKey)\n  if (cached) {\n    callback?.(cached)\n    return cached\n  }\n\n  if (callback) {\n    const subs = subscribers.get(tokensCacheKey)\n    if (subs) {\n      subs.add(callback)\n    } else {\n      subscribers.set(tokensCacheKey, new Set([callback]))\n    }\n  }\n\n  if (!pending.has(tokensCacheKey)) {\n    const job = tokenize(text, language)\n      .then((tokenized) => {\n        tokensCache.set(tokensCacheKey, tokenized)\n        const subs = subscribers.get(tokensCacheKey)\n        subscribers.delete(tokensCacheKey)\n        if (subs) {\n          for (const sub of subs) {\n            sub(tokenized)\n          }\n        }\n      })\n      .catch((error: unknown) => {\n        console.error(\"Failed to highlight code:\", error)\n        subscribers.delete(tokensCacheKey)\n      })\n      .finally(() => {\n        pending.delete(tokensCacheKey)\n      })\n    pending.set(tokensCacheKey, job)\n  }\n\n  return null\n}\n\nconst CodeBlockBody = memo(\n  ({\n    tokenized,\n    showLineNumbers,\n    className,\n  }: {\n    tokenized: TokenizedCode\n    showLineNumbers: boolean\n    className?: string\n  }) => {\n    const preStyle = useMemo(\n      () =>\n        ({\n          backgroundColor: tokenized.bg,\n          color: tokenized.fg,\n          ...tokenized.vars,\n        }) as CSSProperties,\n      [tokenized]\n    )\n\n    const keyedLines = useMemo(\n      () => addKeysToTokens(tokenized.tokens),\n      [tokenized.tokens]\n    )\n\n    const codeStyle = useMemo(\n      () =>\n        showLineNumbers\n          ? ({\n              \"--line-digits\": String(keyedLines.length).length,\n            } as CSSProperties)\n          : undefined,\n      [showLineNumbers, keyedLines.length]\n    )\n\n    return (\n      <pre\n        className={cn(\n          \"m-0 p-4 text-sm dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]\",\n          className\n        )}\n        style={preStyle}\n      >\n        <code\n          className={cn(\n            \"font-mono text-sm\",\n            showLineNumbers && \"[counter-increment:line_0] [counter-reset:line]\"\n          )}\n          style={codeStyle}\n        >\n          {keyedLines.map((keyedLine) => (\n            <LineSpan\n              key={keyedLine.key}\n              keyedLine={keyedLine}\n              showLineNumbers={showLineNumbers}\n            />\n          ))}\n        </code>\n      </pre>\n    )\n  },\n  (prevProps, nextProps) =>\n    prevProps.tokenized === nextProps.tokenized &&\n    prevProps.showLineNumbers === nextProps.showLineNumbers &&\n    prevProps.className === nextProps.className\n)\n\nCodeBlockBody.displayName = \"CodeBlockBody\"\n\nexport const CodeBlockContainer = ({\n  className,\n  language,\n  style,\n  ...props\n}: HTMLAttributes<HTMLDivElement> & { language: string }) => (\n  <div\n    className={cn(\n      \"group relative w-full overflow-hidden rounded-md border bg-background text-foreground\",\n      className\n    )}\n    data-language={language}\n    style={{\n      containIntrinsicSize: \"auto 200px\",\n      contentVisibility: \"auto\",\n      ...style,\n    }}\n    {...props}\n  />\n)\n\nexport const CodeBlockHeader = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex items-center justify-between border-b bg-muted/50 px-3 py-2 text-xs text-muted-foreground\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport const CodeBlockTitle = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport const CodeBlockFilename = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLSpanElement>) => (\n  <span className={cn(\"font-mono\", className)} {...props}>\n    {children}\n  </span>\n)\n\nexport const CodeBlockActions = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"-my-1 -mr-1 flex items-center gap-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport type CodeBlockContentProps = {\n  code: string\n  language: BundledLanguage\n  showLineNumbers?: boolean\n  /**\n   * Accessible name of the scroll container when the code overflows.\n   * Defaults to \"<Language> code\" (\"TypeScript code\"), or \"Code\" when the\n   * language is not a shiki grammar.\n   */\n  \"aria-label\"?: string | undefined\n}\n\nexport const CodeBlockContent = ({\n  code,\n  language,\n  showLineNumbers = false,\n  \"aria-label\": ariaLabel,\n}: CodeBlockContentProps) => {\n  const text = toCodeString(code)\n\n  // Memoized raw tokens for immediate display\n  const rawTokens = useMemo(() => createRawTokens(text), [text])\n\n  // Synchronous cache lookup — avoids setState in effect for cached results.\n  // Skipped on the server and during hydration: the server's module cache\n  // warms across requests, so reading it there makes SSR output diverge from\n  // the client's first render (uifiles change; upstream hydrates inconsistently).\n  const isHydrated = useSyncExternalStore(\n    subscribeToNothing,\n    () => true,\n    () => false\n  )\n  const syncTokens = useMemo(\n    () => (isHydrated ? highlightCode(text, language) : null) ?? rawTokens,\n    [text, language, rawTokens, isHydrated]\n  )\n\n  // Async highlighting result (populated after shiki loads)\n  const [asyncTokens, setAsyncTokens] = useState<TokenizedCode | null>(null)\n  const asyncKeyRef = useRef({ code: text, language })\n\n  // Invalidate stale async tokens synchronously during render\n  if (\n    asyncKeyRef.current.code !== text ||\n    asyncKeyRef.current.language !== language\n  ) {\n    asyncKeyRef.current = { code: text, language }\n    setAsyncTokens(null)\n  }\n\n  useEffect(() => {\n    let canceled = false\n\n    highlightCode(text, language, (result) => {\n      if (!canceled) {\n        setAsyncTokens(result)\n      }\n    })\n\n    return () => {\n      canceled = true\n    }\n  }, [text, language])\n\n  const tokenized = asyncTokens ?? syncTokens\n\n  // A block that overflows must be a tab stop so keyboard users can scroll\n  // it (axe scrollable-region-focusable). It is a named group, not a region:\n  // a landmark per block would trip axe landmark-unique as soon as a page\n  // has two. Measured after mount and again when the container resizes, the\n  // content changes (highlighted tokens replacing raw text, streamed code)\n  // or the fonts finish loading; the attributes go through React state so\n  // server HTML and hydration match.\n  const scrollerRef = useRef<HTMLDivElement>(null)\n  const [scrollable, setScrollable] = useState(false)\n\n  useEffect(() => {\n    const scroller = scrollerRef.current\n    if (!scroller) return\n    let disposed = false\n    const check = () => {\n      if (disposed) return\n      setScrollable(\n        scroller.scrollWidth > scroller.clientWidth ||\n          scroller.scrollHeight > scroller.clientHeight\n      )\n    }\n    check()\n    const resizeObserver = new ResizeObserver(check)\n    resizeObserver.observe(scroller)\n    const mutationObserver = new MutationObserver(check)\n    mutationObserver.observe(scroller, {\n      characterData: true,\n      childList: true,\n      subtree: true,\n    })\n    document.fonts?.ready.then(check, () => undefined)\n    return () => {\n      disposed = true\n      resizeObserver.disconnect()\n      mutationObserver.disconnect()\n    }\n  }, [])\n\n  return (\n    <div\n      className=\"relative overflow-auto focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring\"\n      data-slot=\"code-block-content\"\n      ref={scrollerRef}\n      {...(scrollable && {\n        \"aria-label\": ariaLabel ?? defaultContentLabel(language),\n        role: \"group\",\n        tabIndex: 0,\n      })}\n    >\n      <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} />\n    </div>\n  )\n}\n\nexport const CodeBlock = ({\n  code,\n  language,\n  showLineNumbers = false,\n  className,\n  children,\n  \"aria-label\": ariaLabel,\n  ...props\n}: CodeBlockProps) => {\n  const text = toCodeString(code)\n  const contextValue = useMemo(() => ({ code: text }), [text])\n\n  return (\n    <CodeBlockContext.Provider value={contextValue}>\n      <CodeBlockContainer className={className} language={language} {...props}>\n        {children}\n        <CodeBlockContent\n          aria-label={ariaLabel}\n          code={text}\n          language={language}\n          showLineNumbers={showLineNumbers}\n        />\n      </CodeBlockContainer>\n    </CodeBlockContext.Provider>\n  )\n}\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void\n  onError?: (error: Error) => void\n  timeout?: number\n}\n\nexport const CodeBlockCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false)\n  const timeoutRef = useRef<number>(0)\n  const { code } = useContext(CodeBlockContext)\n\n  const copyToClipboard = useCallback(async () => {\n    if (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n      onError?.(new Error(\"Clipboard API not available\"))\n      return\n    }\n\n    try {\n      if (!isCopied) {\n        await navigator.clipboard.writeText(code)\n        setIsCopied(true)\n        onCopy?.()\n        timeoutRef.current = window.setTimeout(\n          () => setIsCopied(false),\n          timeout\n        )\n      }\n    } catch (error) {\n      onError?.(error instanceof Error ? error : new Error(String(error)))\n    }\n  }, [code, onCopy, onError, timeout, isCopied])\n\n  useEffect(\n    () => () => {\n      window.clearTimeout(timeoutRef.current)\n    },\n    []\n  )\n\n  const Icon = isCopied ? CheckIcon : CopyIcon\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon size={14} />}\n    </Button>\n  )\n}\n\n// Generic over the value so `useState<string | null>` setters and literal\n// unions both type-check against Base UI's `(value | null, eventDetails)`.\nexport type CodeBlockLanguageSelectorProps<Value extends string = string> =\n  ComponentProps<typeof Select<Value, false>>\n\nexport const CodeBlockLanguageSelector = <Value extends string = string>(\n  props: CodeBlockLanguageSelectorProps<Value>\n) => <Select {...props} />\n\nexport type CodeBlockLanguageSelectorTriggerProps = ComponentProps<\n  typeof SelectTrigger\n>\n\nexport const CodeBlockLanguageSelectorTrigger = ({\n  className,\n  ...props\n}: CodeBlockLanguageSelectorTriggerProps) => (\n  <SelectTrigger\n    className={cn(\n      \"h-7 border-none bg-transparent px-2 text-xs shadow-none\",\n      className\n    )}\n    size=\"sm\"\n    {...props}\n  />\n)\n\nexport type CodeBlockLanguageSelectorValueProps = ComponentProps<\n  typeof SelectValue\n>\n\nexport const CodeBlockLanguageSelectorValue = (\n  props: CodeBlockLanguageSelectorValueProps\n) => <SelectValue {...props} />\n\nexport type CodeBlockLanguageSelectorContentProps = ComponentProps<\n  typeof SelectContent\n>\n\nexport const CodeBlockLanguageSelectorContent = ({\n  align = \"end\",\n  ...props\n}: CodeBlockLanguageSelectorContentProps) => (\n  <SelectContent align={align} {...props} />\n)\n\nexport type CodeBlockLanguageSelectorItemProps = ComponentProps<\n  typeof SelectItem\n>\n\nexport const CodeBlockLanguageSelectorItem = (\n  props: CodeBlockLanguageSelectorItemProps\n) => <SelectItem {...props} />\n",
      "type": "registry:component",
      "target": "components/ai/code-block.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. CodeBlockLanguageSelector is Base UI Select.Root and is generic over the value: onValueChange is (value: Value | null, eventDetails) instead of (value: string), so a useState<string | null> setter or a literal-union state type-checks without casts (guard null before narrowing); pass `items` (Record<value, label>) to Select so CodeBlockLanguageSelectorValue renders the label instead of the raw value. CodeBlockLanguageSelectorContent forwards side/sideOffset/align/alignOffset to the positioner; Radix `position=\"popper\"` is `alignItemWithTrigger={false}` (the wrapper defaults to true). Copy button is the Base UI Button (use `render` instead of `asChild`). Header background is bg-muted/50 (upstream bg-muted/80) so 12px muted-foreground text meets 4.5:1 in the light theme. uifiles changes: a block whose code overflows becomes a focusable scroll container (tabIndex 0, role=\"group\", not a landmark, so two overflowing blocks on one page do not trip axe landmark-unique) named by `aria-label` (default \"<Language> code\" from shiki's display name, e.g. \"TypeScript code\" for `typescript` or `ts`, and \"Code\" for text/plaintext or an unknown language; `aria-label` on CodeBlock is routed to that container, not the wrapper); an unknown or empty `language` renders as plain text instead of rejecting, checked as an own key of shiki's bundled languages so names that exist on Object.prototype (`constructor`, `toString`, `hasOwnProperty`) count as unknown too (one console.warn per unknown language, none for an empty one); `code` that is undefined/null renders as empty; CRLF input renders as the same lines before and after highlighting; the token cache is keyed on the full code and the language (NUL-separated, so a colon in a fence info string cannot collide), one highlight runs per snippet, and highlightCode invokes its callback synchronously on a cache hit; every block on the page shares one Shiki highlighter, created with both themes on first use, and each grammar loads on demand the first time its language appears (a failed start or grammar load is retried by the next block that needs it), so a transcript in many languages loads the themes once and never trips Shiki's console warning about ten or more highlighter instances (upstream creates a highlighter per language string, so `ts` and `typescript` count twice); line numbers use the full muted-foreground color and the gutter width follows the digit count.",
  "categories": [
    "ai",
    "chat",
    "code"
  ],
  "type": "registry:component"
}