Code block

Free · MIT

Syntax-colored code from a built-in tokenizer or your own tokens, with theme-derived colors, line numbers, highlighted lines, a file tab, copy with a drawn check, a wrap toggle and a staggered line entrance.

MarkdownOpen in v0
Open in v0

Installation

$ npx shadcn@latest add https://goodui.dev/r/code-block.json

The file lands in components/goodui/code-block.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/code-block works too.

Usage

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

<CodeBlock
  fileName="app/api/bookings/route.ts"
  language="ts"
  highlightLines={[9, 10]}
  maxHeight={380}
  code={source}
/>

When to use

Use it for
  • Code in assistant answers, docs and settings pages, with copy and wrap built in.
  • Short snippets in a dozen common languages without shipping a highlighter.
  • Pre-highlighted tokens from shiki or prism when you already run one: pass tokens and keep the colors consistent.
Reach for something else
  • Editable code: use an editor component.
  • Highlighting for rare languages with exact grammar needs: run shiki and pass tokens.
If you needUse
Snippet in a messagecode + language (chat-message does this for fences)
File contextfileName="lib/pricing.ts"
Draw attention to lineshighlightLines={[9, 10]}
Your own highlightertokens={lines} with CodeToken types
Answer that appears progressivelyappear="lines"

Examples

Tokens from shiki

const { tokens } = await highlighter.codeToTokens(code, { lang: "tsx", theme: "github-light" })
const lines = tokens.map((line) => line.map((t) => ({ content: t.content, type: typeOf(t) })))
<CodeBlock code={code} tokens={lines} />

Minimal: no header controls

<CodeBlock code={cmd} language="bash" showLineNumbers={false} wrapToggle={false} />

Reuse the tokenizer

import { tokenizeCode, CodeTokens } from "@/components/goodui/code-block"
const lines = tokenizeCode(src, "json")

API reference

CodeBlock

PropTypeDefault
code*
Source text. Tokenized with the built-in tokenizer unless `tokens` is passed.
string—
title
Replaces the language label next to the file tab.
string—
onCopy(code: string) => void—
language"ts" | "tsx" | "js" | "jsx" | "json" | "bash" | "sh" | "python" | "py" | "css" | "html" | "sql" | "yaml" | "yml" | "text""text"
tokens
Pre-tokenized lines from your own highlighter; each token's type picks the color. Overrides the built-in tokenizer.
CodeToken[][]—
fileName
Shown as a tab in the header, e.g. "lib/booking.ts".
string—
showLineNumbersbooleantrue
startLine
Number of the first line.
number1
highlightLines
1-based line numbers (counted from startLine) to highlight.
number[]—
wrap
Controlled wrap state.
boolean—
defaultWrapbooleanfalse
onWrapChange(wrap: boolean) => void—
wrapToggle
Show the wrap toggle.
booleantrue
copy
Show the copy button.
booleantrue
maxHeight
Scroll inside the block beyond this height (px).
number—
appear
Entrance: lines fade up one after another, the whole block fades, or nothing.
"none" | "fade" | "lines""none"
labels
Text for built-in controls and screen readers; pass translations here.
Partial<CodeBlockLabels>—

Also accepts every <div> attribute.

CodeTokens

PropTypeDefault
tokens*CodeToken[]—

Accessibility

TabMoves between the wrap toggle and the copy button; Enter or Space activates them.
  • The copy button is labelled Copy code and announces Copied through a polite live region; the wrap toggle uses aria-pressed.
  • Line numbers are aria-hidden and not selectable, so copying a selection gives clean code.
  • Token colors are mixed from --chart-1..5 toward the foreground in light mode and toward white in dark mode to keep 4.5:1 contrast on the card background.
  • Built-in strings go through the labels prop for translation.

Motion

  • appear="lines" fades each line up 4px with a 28 ms stagger (capped at 24 lines); appear="fade" fades the whole block; the default is static.
  • The copy icon scales out as a green check scales in (200 ms) and reverts after 1.6 s.
  • Reduced motion: no entrance animation; the icon swap is instant.
  • The tokenizer is a sticky-regex scanner, one pass per line with a small carry-over state for block comments and template strings; results are memoised on code and language.

Notes for AI

Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.

  • Supported languages: ts, tsx, js, jsx, json, bash/sh, python/py, css, html, sql, yaml/yml, text. Unknown languages render plain.
  • highlightLines are 1-based and count from startLine.
  • Pass tokens (CodeToken[][]) to skip the built-in tokenizer; types are keyword, string, number, comment, function, type, property, punctuation, operator, tag, attr, variable, constant, plain.
  • maxHeight makes the body scroll; wrap turns off horizontal scrolling by wrapping lines.

FAQ

Is the built-in tokenizer accurate?

It is a lightweight scanner tuned for readable colors, not a parser. JSX attributes and regex literals are not special-cased. For exact grammars, pass tokens from shiki.

Can I theme the colors?

Yes. They derive from --chart-1..5; override the --pc-code-* variables on .pc-code for finer control.

Related