HextaUI

Code Block

Code blocks built for AI answers. Syntax highlighting that keeps up with streaming, copy, download and wrap, line numbers and highlighted lines, diffs with accept and reject, and a terminal for commands.

  1. Add the Pro registry to components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Add your token

    Create a token on your account page and put it in .env.local as HEXTAUI_PRO_TOKEN.

  3. Add the block

    pnpm dlx shadcn@latest add @hextaui-pro/code-block

In Markdown with Streamdown

Register CodeFence as a renderer. It reads the language and fence settings like title="app/page.tsx", {2,4-6} and showLineNumbers, and keeps highlighting while the fence is still streaming.

"use client"

import { Streamdown } from "streamdown"

import { CodeFence, codeFenceLanguages } from "@/components/blocks/code-block/code-fence"

export function Answer({
  markdown,
  streaming,
}: {
  markdown: string
  streaming: boolean
}) {
  return (
    <Streamdown
      mode="streaming"
      isAnimating={streaming}
      plugins={{
        renderers: [{ language: codeFenceLanguages, component: CodeFence }],
      }}
    >
      {markdown}
    </Streamdown>
  )
}

Reviewing a change

Show a proposed edit as a diff and let the reader accept or reject it, with ⌘↵ and ⌘⌫ while the block has focus.

"use client"

import * as React from "react"

import { CodeBlock, type ReviewStatus } from "@/components/blocks/code-block/code-block"

export function ProposedEdit({
  patch,
  onAccept,
  onReject,
}: {
  patch: string
  onAccept: () => Promise<void>
  onReject: () => void
}) {
  const [status, setStatus] = React.useState<ReviewStatus>("pending")

  return (
    <CodeBlock
      code={patch}
      language="tsx"
      filename="components/search.tsx"
      diff
      review={{
        status,
        onAccept: async () => {
          await onAccept()
          setStatus("accepted")
        },
        onReject: () => {
          onReject()
          setStatus("rejected")
        },
      }}
    />
  )
}

On its own

Pass the code and language directly, for example when a tool call returns file contents.

import { CodeBlock } from "@/components/blocks/code-block/code-block"

export function FileContents({ path, contents }: { path: string; contents: string }) {
  return (
    <CodeBlock
      code={contents}
      language={path.split(".").pop()}
      filename={path}
      lineNumbers
      highlight={[3, 4]}
    />
  )
}

Code

9 files, added to components/blocks/code-block.