GitHub

Code

A virtualized code and log viewer with line numbers, source-linked highlights, zoom, retry, and download.

The Code Viewer renders code-like plain text, logs, and source-linked extraction text with stable line numbers. It loads a bounded text resource up front so every line can be addressed, then renders a virtualized fixed-height line window so large logs stay scrollable.

Installation

pnpm dlx shadcn@latest add @retab/code-viewer

Usage

import { CodeViewer } from "@/components/ui/code-viewer";
 
export function Example() {
  return (
    <CodeViewer
      source={{
        kind: "url",
        url: "/samples/extraction-run.log",
        fileName: "extraction-run.log",
      }}
      highlight={{ start: 12, end: 14 }}
    />
  );
}

Inline text is supported too:

<CodeViewer
  source={{
    kind: "text",
    text: "first line\nsecond line",
    fileName: "inline.log",
  }}
/>

Features

  • Virtualized line rendering — only the visible line window plus overscan is mounted, so large logs do not create thousands of DOM nodes.
  • Stable line addressinghighlight and scrollToLineRange use 1-based inclusive line ranges.
  • Resource-based sources — URL, Blob, and inline text sources share the same bounded loading path.
  • Local error states — failed URL loads can retry without tearing down the surrounding page.
  • Zoom and download — the controls expose text scale controls and downloads from the source resource.

Languages

Syntax highlighting is powered by Prism. A curated set of common languages ships by default — JSON, JavaScript / JSX, TypeScript / TSX, Python, YAML, Bash, SQL, Go, Rust, Java, Markdown, CSS, and HTML / XML — resolved from the source's file extension (falling back to its MIME type for inline sources without one). A file whose extension is not in the table renders as plain, unhighlighted text.

The viewer tokenizes each line independently so highlighting stays cheap even on very large files — a deliberate trade-off that means multi-line constructs (template literals, block comments, docstrings) are highlighted per line rather than across line boundaries.

To add another language, import its Prism component once — at your app entry point or directly in code-viewer-syntax.ts (prismjs is already installed) — and add a row mapping the file extension to the Prism language id:

// in code-viewer-syntax.ts
import "prismjs/components/prism-kotlin";
 
const LANGUAGE_BY_EXTENSION: Record<string, string> = {
  // …languages that ship by default
  kt: "kotlin",
};

Token colors are CSS variables (--cv-token-keyword, --cv-token-string, --cv-token-comment, …) with light and dark defaults, so a theme can override any of them. A token kind a language emits that has no color rule simply inherits the foreground color.

API Reference

PropTypeDescription
sourceCodeDocumentSourceRequired source descriptor. Supports URL, Blob, and inline text resources.
classNamestringOptional class on the viewer container.
controlsbooleanShow line count, zoom controls, and download action. Defaults to true.
downloadbooleanShow the download action in the controls. Defaults to true.
highlightCodeLineRange | null1-based inclusive line range to highlight.
barebooleanDrop the outer border/background so the viewer fills its container.
maxBytesnumberMaximum text payload size accepted by the viewer.
maxLinesnumberMaximum number of lines accepted by the viewer.

Ref

import * as React from "react";
 
import { CodeViewer, type CodeViewerHandle } from "@/components/ui/code-viewer";
 
export function Example() {
  const viewerRef = React.useRef<CodeViewerHandle>(null);
 
  return (
    <CodeViewer
      ref={viewerRef}
      source={{ kind: "text", text: "alpha\nbeta\ngamma" }}
      highlight={{ start: 2, end: 2 }}
    />
  );
}

scrollToLineRange(range, options?) scrolls any normalized line range into view, even when that line is outside the currently mounted virtual window.

getViewportElement() returns the scroll viewport element, or null before mount — useful for scroll syncing and measurement.

Source

code-viewer.tsx