# Code Viewer

Syntax-highlighted code with folding, line highlights, copying, and streaming feedback.

Page: https://sui.draco.dev/docs/components/code-viewer

### Example: code-viewer-demo

```tsx
"use client";
import { CodeViewer } from "@workspace/ui/components/code-viewer";
import type { ExampleProps } from "../types";

const code =
  'const colors = ["bamboo", "mauve", "mist"];\n\nexport function getTheme(name: string) {\n  if (colors.includes(name)) {\n    return { name, active: true };\n  }\n  return { name: "default", active: false };\n}';
export default function Example({ locale }: ExampleProps) {
  const chinese = locale === "zh-CN";
  return (
    <CodeViewer
      code={code}
      lang="typescript"
      title="theme.ts"
      highlightLines={[4, 5]}
      maxHeight={240}
      labels={
        chinese
          ? {
              copy: "复制代码",
              loading: "正在高亮",
              error: "无法高亮代码",
              empty: "没有代码",
              expand: "展开",
              collapse: "折叠",
              writing: "正在编写",
              ready: "就绪",
              copied: "已复制",
              copyFailed: "复制失败",
            }
          : undefined
      }
    />
  );
}
```

## Installation

```bash
bunx --bun shadcn@latest add @sui/code-viewer
```

Install with the shadcn CLI or use the shared `@workspace/ui` package. Follow the [installation guide](/docs/installation) to configure the registry, load styles, and choose import aliases.

## Usage

```tsx
import { CodeViewer } from "@workspace/ui/components/code-viewer";

<CodeViewer code={'const theme = "bamboo";'} lang="typescript" title="theme.ts" />;
```

## Lines, folding, and layout

Line numbers are shown by default. `highlightLines` uses one-based line numbers. Indented blocks following `{`, `[`, or `(` can be folded with the gutter controls; changing `code` resets folding. This is an indentation-based view, not a syntax-aware code editor.

Set `wrap` for long lines or change `maxHeight` to limit the scrolling viewport. Use `showLineNumbers={false}` for compact snippets. The viewport is reachable with Tab and supports keyboard scrolling with a visible theme-colored focus ring.

## Streaming content

Pass `status="streaming"` while appending code, then switch to `"complete"`. The viewer displays status feedback and follows content near the bottom. Scrolling more than 32px away pauses following; returning to the bottom resumes it. Starting a new streaming session restores following by default. The example replays a local sequence and cleans up its timer on unmount.

### Example: code-viewer-streaming

```tsx
"use client";
import { Button } from "@workspace/ui/components/button";
import { CodeViewer } from "@workspace/ui/components/code-viewer";
import { useEffect, useState } from "react";
import type { ExampleProps } from "../types";

const lines = [
  'const colors = ["bamboo", "mauve", "mist"];',
  "",
  "export function getTheme(name: string) {",
  "  if (colors.includes(name)) {",
  "    return { name, active: true };",
  "  }",
  '  return { name: "default", active: false };',
  "}",
  "",
  "export const settings = {",
  '  theme: "bamboo",',
  "  palettes: [",
  '    "bamboo",',
  '    "mauve",',
  '    "mist",',
  '    "sand",',
  '    "pine",',
  '    "rose",',
  "  ],",
  "  layout: {",
  '    density: "comfortable",',
  '    width: "content",',
  "  },",
  "};",
];
export default function Example({ locale }: ExampleProps) {
  const chinese = locale === "zh-CN";
  const [count, setCount] = useState(lines.length);
  const streaming = count < lines.length;
  useEffect(() => {
    if (!streaming) return;
    const timer = setTimeout(() => setCount(count + 1), 240);
    return () => clearTimeout(timer);
  }, [streaming, count]);
  return (
    <div className="grid w-full gap-3">
      <Button
        type="button"
        variant="outline"
        className="justify-self-start"
        disabled={streaming}
        onClick={() => setCount(1)}
      >
        {chinese ? "重新播放流式输入" : "Replay streaming input"}
      </Button>
      <CodeViewer
        code={lines.slice(0, count).join("\n")}
        status={streaming ? "streaming" : "complete"}
        lang="typescript"
        title="theme.ts"
        wrap
        showLineNumbers={false}
        maxHeight={180}
        labels={
          chinese
            ? {
                copy: "复制代码",
                loading: "正在高亮",
                error: "无法高亮代码",
                empty: "没有代码",
                expand: "展开",
                collapse: "折叠",
                writing: "正在编写",
                ready: "就绪",
                copied: "已复制",
                copyFailed: "复制失败",
              }
            : undefined
        }
      />
    </div>
  );
}
```

## Copy and theme behavior

Copy writes the original complete code, including folded lines, and provides success or failure feedback. Set `copyable={false}` to remove the action. Highlighting loads asynchronously with a bounded skeleton; streaming updates keep incoming text visible while tokens are prepared. If loading is pending, the complete source remains available to copy and to assistive technology; if highlighting fails, readable plain code remains available. An empty string displays the empty-state label.

The viewer follows the surrounding light or dark theme unless `theme` is supplied. Translate `labels` for copy, loading, empty, folding, writing, ready, copied, and copy failure.

## Plain style and highlighting configuration

`variant="plain"` removes the outer frame and title bar while retaining the code and copy action. Override `--code-highlight-bg` to customize highlighted-line backgrounds.

An optional `ShikiProvider` shares highlighting configuration across its viewers and editors. `themes` provides light and dark themes, and `languages` preloads requested languages. Highlighting works without a provider; recognized languages and aliases load on demand, while unknown languages fall back to plain text.

### Example: code-viewer-plain

```tsx
"use client";
import {
  CodeViewer,
  ShikiProvider,
} from "@workspace/ui/components/code-viewer";
import type { ExampleProps } from "../types";

const themes = { light: "github-light", dark: "github-dark" } as const;
const languages = ["python", "sql"] as const;
const code =
  'def greeting(name: str):\n    return f"Hello, {name}"\n\nprint(greeting("SUI"))';
export default function Example({ locale }: ExampleProps) {
  const chinese = locale === "zh-CN";
  return (
    <ShikiProvider themes={themes} languages={languages}>
      <div className="w-full rounded-xl bg-muted p-4">
        <CodeViewer
          code={code}
          lang="python"
          variant="plain"
          highlightLines={[2]}
          className="[--code-highlight-bg:var(--accent)]"
          labels={
            chinese
              ? {
                  copy: "复制代码",
                  copied: "已复制",
                  copyFailed: "复制失败",
                  loading: "正在高亮",
                  error: "无法高亮代码",
                  empty: "没有代码",
                  expand: "展开",
                  collapse: "折叠",
                  writing: "正在编写",
                  ready: "就绪",
                }
              : undefined
          }
        />
      </div>
    </ShikiProvider>
  );
}
```

```tsx
import { CodeViewer, ShikiProvider } from "@workspace/ui/components/code-viewer";

<ShikiProvider
  themes={{ light: "github-light", dark: "github-dark" }}
  languages={["python", "sql"]}
>
  <CodeViewer
    code={code}
    lang="python"
    variant="plain"
    highlightLines={[2]}
    className="[--code-highlight-bg:var(--accent)]"
  />
</ShikiProvider>;
```

## API reference

| Prop | Type | Default / behavior |
| --- | --- | --- |
| `variant` | `"default" \| "plain"` | `"default"`. |
| `code` | `string` | Required source text. |
| `lang` | `string` | `"typescript"`. |
| `theme` | `"light" \| "dark"` | Follows surrounding theme. |
| `title` | `ReactNode` | Optional header title. |
| `status` | `"streaming" \| "complete"` | `"complete"`. |
| `showLineNumbers` | `boolean` | `true`. |
| `highlightLines` | `number[]` | `[]`; one-based. |
| `maxHeight` | `number` | `280` pixels. |
| `wrap` | `boolean` | `false`. |
| `copyable` | `boolean` | `true`. |
| `labels` | `Partial<CodeViewerLabels>` | English labels. |
| `glass` | `boolean` | `false`. |

The module exports `CodeViewerProps` and `CodeViewerLabels`. Highlighting uses [Shiki](https://shiki.style/guide/). To edit instead of view, use [Editor](/docs/components/editor).

`ShikiProviderProps` is also exported from this module; theme names use Shiki bundled themes.

### ShikiProvider

| Prop | Type | Default / behavior |
| --- | --- | --- |
| `themes` | `{ light: BundledTheme; dark: BundledTheme }` | `one-light` and `one-dark-pro`. |
| `languages` | `readonly string[]` | Optional languages to preload. |
| `onError` | `(error: unknown) => void` | Reports preload failure without changing the plain-text fallback. |

Keep static `themes` and `languages` outside the component to avoid restarting preloads.
