Skip to content

Pro pre-sale is open. Installation opens January 2027. See Pro

Components

CodeBlock

A block of code to read and copy: an optional header with the file name and the language, a copy button that says when it copied, line numbers, highlighted lines, and long lines that scroll sideways or wrap. A small built-in highlighter colors TS, JS, JSON, CSS, HTML and shell with text tokens; pass highlight to plug in another one.

  • npm: 2 direct, 4 total
  • registry: 3 direct, 3 total

Preview

app/page.tsxtsx
import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

Props

  • language
  • filename
  • showLanguage
  • variant
  • size
  • showLineNumbers
  • defaultWrap
  • showWrapButton
  • showCopyButton
  • sample
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleTsx = "import { Button } from \"@/components/ui/button\"\n\nexport default function Page() {\n  // Save the draft before leaving.\n  return <Button variant=\"solid\" onClick={() => save(42)}>Save</Button>\n}"
<CodeBlock language="tsx" filename="app/page.tsx" showLineNumbers code={codeSampleTsx} />

Usage

When to use

  • Show code to read or copy in docs, a setup guide or a changelog.
  • Point at the lines of a file that matter, with line numbers and highlighted lines.

When not to use

  • A word or a short name inside a sentence. Use a code element in the text.
  • Code the reader edits. Use a code editor or a Textarea.
  • A long log that streams in. Use a scrolling log view.

Variants

variant

default sits sunken in a page or card, surface stands on a sunken area, plain has no fill and no edge, for code inside another panel.

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    defaultdefault

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    surface

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    plain

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

const codeSampleTsx = "import { Button } from \"@/components/ui/button\"\n\nexport default function Page() {\n  // Save the draft before leaving.\n  return <Button variant=\"solid\" onClick={() => save(42)}>Save</Button>\n}"
<CodeBlock language="tsx" filename="app/page.tsx" showLineNumbers code={codeSampleTsx} />
<CodeBlock language="tsx" filename="app/page.tsx" variant="surface" showLineNumbers code={codeSampleTsx} />
<CodeBlock language="tsx" filename="app/page.tsx" variant="plain" showLineNumbers code={codeSampleTsx} />

size

Code text, line height and padding: 12, 14 or 16px text.

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    sm

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    mddefault

  • app/page.tsxtsx
    import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}

    lg

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

const codeSampleTsx = "import { Button } from \"@/components/ui/button\"\n\nexport default function Page() {\n  // Save the draft before leaving.\n  return <Button variant=\"solid\" onClick={() => save(42)}>Save</Button>\n}"
<CodeBlock language="tsx" filename="app/page.tsx" size="sm" showLineNumbers code={codeSampleTsx} />
<CodeBlock language="tsx" filename="app/page.tsx" showLineNumbers code={codeSampleTsx} />
<CodeBlock language="tsx" filename="app/page.tsx" size="lg" showLineNumbers code={codeSampleTsx} />

Examples

File

A file name and a language in the header, with the copy button at the end.

app/page.tsxtsx
import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving.  return <Button variant="solid" onClick={() => save(42)}>Save</Button>}
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleTsx = "import { Button } from \"@/components/ui/button\"\n\nexport default function Page() {\n  // Save the draft before leaving.\n  return <Button variant=\"solid\" onClick={() => save(42)}>Save</Button>\n}"
<CodeBlock filename="app/page.tsx" language="tsx" code={codeSampleTsx} />

Line numbers and highlighted lines

highlightLines marks lines with the info fill and an info bar; count them from startLine.

app/page.tsxtsx
import { Button } from "@/components/ui/button"export default function Page() {  // Save the draft before leaving. (highlighted)  return <Button variant="solid" onClick={() => save(42)}>Save</Button> (highlighted)}
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleTsx = "import { Button } from \"@/components/ui/button\"\n\nexport default function Page() {\n  // Save the draft before leaving.\n  return <Button variant=\"solid\" onClick={() => save(42)}>Save</Button>\n}"
<CodeBlock filename="app/page.tsx" language="tsx" code={codeSampleTsx} showLineNumbers highlightLines={[4, 5]} />

Excerpt

startLine numbers an excerpt from where it sits in the file.

tokens.csscss
:root {  --radius-container: 12px; (highlighted)  --spacing-lg: 16px;}
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleCss = ":root {\n  --radius-container: 12px;\n  --spacing-lg: 16px;\n}"
<CodeBlock filename="tokens.css" language="css" code={codeSampleCss} showLineNumbers startLine={42} highlightLines={[43]} />

Wrap

Long lines scroll sideways by default. showWrapButton adds a toggle; defaultWrap starts wrapped.

bash
npx shadcn@latest add https://ui.example.com/r/code-block.json https://ui.example.com/r/button.json --overwrite --yes
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleLong = "npx shadcn@latest add https://ui.example.com/r/code-block.json https://ui.example.com/r/button.json --overwrite --yes"
<CodeBlock language="bash" code={codeSampleLong} showWrapButton />

Without a header

With no file name or language the copy button floats at the top end corner.

npx shadcn@latest add @werewolf/code-block
import { CodeBlock } from "@/components/ui/code-block"

<CodeBlock language="bash" showLanguage={false} code="npx shadcn@latest add @werewolf/code-block" />

Plain text

highlight={false} turns the colors off; a language with no built-in grammar renders plain too.

notes.txt
Werewolf UITokens first, components second.Every color comes from a token.
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleText = "Werewolf UI\nTokens first, components second.\nEvery color comes from a token."
<CodeBlock filename="notes.txt" highlight={false} code={codeSampleText} />

Your own highlighter

Pass a function that returns tokens per line, such as one built from a server-side highlighter's output.

sql
SELECT name, emailFROM usersWHERE active = trueORDER BY name;
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleSql = "SELECT name, email\nFROM users\nWHERE active = true\nORDER BY name;"
const SQL_KEYWORDS = /\b(SELECT|FROM|WHERE|ORDER|BY|AND|OR|true|false)\b/
const highlightSql: CodeBlockHighlighter = (code) =>
  code.split("\n").map((line) =>
    line
      .split(/(\b(?:SELECT|FROM|WHERE|ORDER|BY|AND|OR|true|false)\b)/)
      .filter(Boolean)
      .map((text) => ({ text, className: SQL_KEYWORDS.test(text) ? "text-text-purple" : undefined }))
  )
<CodeBlock language="sql" code={codeSampleSql} highlight={highlightSql} />

Tall code

maxHeight caps the code; it scrolls both ways and takes focus for the keyboard.

package.jsonjson
{  "name": "@werewolf/code-block",  "type": "registry:ui",  "private": false,  "version": 1,  "dependencies": ["@tabler/icons-react"],  "registryDependencies": ["@werewolf/utils", "@werewolf/button"]}
import { CodeBlock } from "@/components/ui/code-block"

const codeSampleJson = "{\n  \"name\": \"@werewolf/code-block\",\n  \"type\": \"registry:ui\",\n  \"private\": false,\n  \"version\": 1,\n  \"dependencies\": [\"@tabler/icons-react\"],\n  \"registryDependencies\": [\"@werewolf/utils\", \"@werewolf/button\"]\n}"
<CodeBlock filename="package.json" language="json" code={codeSampleJson} showLineNumbers maxHeight="160px" />

Do and don't

Do

  • Name the file when the code belongs in one, so the reader knows where to paste it.
  • Highlight the lines that changed or that the text talks about, and say so in the text.
  • Keep long lines scrolling for code people copy; offer the wrap toggle for reading on narrow screens.

Don't

  • Do not put a CodeBlock inside a sentence; use a code element for a name or a short command.
  • Do not rely on color to tell lines apart; highlighted lines need words around them.
  • Do not add a heavy highlighter just for color; the built-in one covers the common languages, and highlight plugs in another one only where needed.

Props

NameTypeValuesDefaultDescription
code requiredstring--The code, as plain text. It is what the copy button copies.
languagestring--The language, such as tsx, json, css, html or bash. Shown in the header, and picks the built-in grammar.
filenamestring--A file name at the start of the header, such as app/page.tsx. It also names the code region.
showLanguageboolean-trueShows the language in the header.
variantenumdefault | surface | plaindefaultdefault sits sunken in a page or card, surface stands on a sunken area, plain has no fill and no edge, for code inside another panel.
sizeenumsm | md | lgmdCode text, line height and padding: 12, 14 or 16px text.
showLineNumbersboolean-falseNumbers each line.
startLinenumber-1The number of the first line, for an excerpt.
highlightLinesnumber[]--Line numbers to mark, counted from startLine, such as [3, 4].
wrapboolean--Long lines wrap instead of scrolling sideways. Controlled; pair with onWrapChange.
defaultWrapboolean-falseWhether long lines start wrapped, uncontrolled.
onWrapChange(wrap: boolean) => void--Called when the wrap toggle is pressed.
showWrapButtonboolean-falseShows a toggle that wraps long lines.
showCopyButtonboolean-trueShows the copy button.
onCopy(code: string) => void--Called after the code is copied.
highlightboolean | CodeBlockHighlighter-truetrue uses the built-in highlighter, false shows plain text, and a function (code, language) => tokens per line plugs in another one, such as a server-side highlighter's tokens.
maxHeightstring--The tallest the code grows before it scrolls, as a CSS length, such as 320px.
labels{ copy?: string; copied?: string; copyFailed?: string; wrap?: string; highlighted?: string; region?: string }--Button names, messages and the words read for a highlighted line and the code region. Defaults: Copy code, Copied, Couldn't copy, Wrap lines, (highlighted), Code. Vietnamese: Sao chép mã, Đã sao chép, Không sao chép được, Xuống dòng, (đánh dấu), Mã.

Anatomy

PartDescription
rootThe figure: a sunken, surface or plain panel holding the header and the code.
headerThe figcaption: file name and language at the start, buttons at the end, over a quiet line.
nameThe file name, in monospace, cut with an ellipsis.
labelThe language, such as tsx, in secondary text.
actionsThe wrap toggle and the copy button, ghost icon Buttons in size sm. Without a header they float at the top end corner.
contentThe pre that scrolls sideways while lines are long. It takes focus so the keyboard can scroll it.
lineOne line of code. A highlighted line takes the info fill across the whole scroll width and an info bar at its start.
lineNumberThe number of the line, in secondary text, right aligned. It turns to the info text on a highlighted line.

Accessibility

Role: figure

KeysAction
TabMoves to the wrap toggle and the copy button, then to the code, which scrolls with the arrow keys while long lines overflow.
Enter / SpaceOn the copy button, copies the code. On the wrap toggle, wraps or unwraps long lines.
  • With a file name or a language the figure is named by its header, so the code is announced as, EN `app/page.tsx tsx`.
  • The copy button is named EN `Copy code`. After a copy its name turns to EN `Copied` for two seconds and a status region announces it.
  • The wrap toggle is a pressed button (aria-pressed), named EN `Wrap lines`.
  • Line numbers are hidden from screen readers and from text selection, so a copied selection holds only code.
  • A highlighted line is shown with the info fill and an info bar at its start, and screen readers hear the word highlighted after it. Say what it means in the text around the block too; color alone does not reach every reader.

Install

npx werewolf-ui add code-block

Run npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.