# Code Block

Code Block component used across Vercel and Next.js.

---

## Default

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `// Usage:
//   enabled by \`--debug-prerender\`
//   route patterns: [id...] or [...id]
//   NODE_OPTIONS='--debug-prerender' node
function MyComponent(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>This is an example React component.</p>
    </div>
  );
}`;

export function Component(): JSX.Element {
  return (
    <CodeBlock aria-label="Hello world" filename="Table.jsx" language="jsx">
      {code}
    </CodeBlock>
  );
}
```

## No filename

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `function MyComponent(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>This is an example React component.</p>
    </div>
  );
}`;

export function Component(): JSX.Element {
  return (
    <CodeBlock aria-label="Hello world" language="jsx">
      {code}
    </CodeBlock>
  );
}
```

## Highlighted lines

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `function MyComponent(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>This is an example React component.</p>
    </div>
  );
}`;

export function Component(): JSX.Element {
  return (
    <CodeBlock
      aria-label="Hello world"
      filename="highlighted.jsx"
      highlightedLinesNumbers={[1, 4]}
      language="jsx"
    >
      {code}
    </CodeBlock>
  );
}
```

## Added & removed lines

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `module.exports = {
  experimental: {
    appDir: true,
  },
  appDir: true,
}`;

export function Component(): JSX.Element {
  return (
    <CodeBlock
      aria-label="Hello world"
      filename="next.config.js"
      addedLinesNumbers={[5]}
      removedLinesNumbers={[2, 3, 4]}
      language="jsx"
    >
      {code}
    </CodeBlock>
  );
}
```

## Referenced lines

You can link to lines. Just press on any line number.

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `function MyComponent(props) {
  return (
    <div>
      <h1>Count: {props.count}</h1>
    </div>
  );
}`;

export function Component(): JSX.Element {
  return (
    <CodeBlock aria-label="Hello world" language="jsx">
      {code}
    </CodeBlock>
  );
}
```

## Language switcher

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import { useState, type JSX } from 'react';

const code = `function MyComponent(props) {
	return (
	  <div>
		<h1>Hello, {props.name}!</h1>
		<p>Good to see you</p>
	  </div>
	);
  }`;

const codeTs = `function MyComponent(props: Props) {
	return (
	  <div>
		<h1>Hello, {props.name}!</h1>
		<p>Good to see you</p>
	  </div>
	);
  }`;

const codeLua = `local b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
local decode_table = ffi.new 'uint8_t[256]'
for i = 1, #b64 do
  decode_table[str_byte(b64, i)] = i - 1 -- Base64 values start from 0
end

function BloomFilter:has(key)
  local ptr = self.ptr -- uint8_t* pointer to start of base64 string
  for byte_offset, bit_offset in self:iterator(key) do
    local sextet = decode_table[ptr[byte_offset]]
    if band(sextet, lshift(1, bit_offset)) == 0 then
      return false
    end
  end
  return true
end`;

const languages = [
  {
    label: 'JavaScript',
    value: 'js',
  },
  {
    label: 'TypeScript',
    value: 'ts',
  },
  {
    label: 'Next.js',
    value: 'next',
  },
  {
    label: 'Lua',
    value: 'lua',
  },
];

export function Component(): JSX.Element {
  const [language, setLanguage] = useState('js');

  if (language === 'ts') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="language-switcher.tsx"
        language="tsx"
        switcher={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeTs}
      </CodeBlock>
    );
  }

  if (language === 'next') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="language-switcher.tsx"
        language="next"
        switcher={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeTs}
      </CodeBlock>
    );
  }

  if (language === 'lua') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="bloom-filter.lua"
        language="lua"
        switcher={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeLua}
      </CodeBlock>
    );
  }

  return (
    <CodeBlock
      aria-label="Hello world"
      filename="language-switcher.jsx"
      language="jsx"
      switcher={{
        options: languages,
        value: language,
        onChange: (l) => setLanguage(l),
      }}
    >
      {code}
    </CodeBlock>
  );
}
```

## Language switcher with tabs

Use the `tabs` prop for a tabbed language switcher instead of the default select.

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import { useState, type JSX } from 'react';

const code = `function MyComponent(props) {
	return (
	  <div>
		<h1>Hello, {props.name}!</h1>
		<p>Good to see you</p>
	  </div>
	);
  }`;

const codeTs = `function MyComponent(props: Props) {
	return (
	  <div>
		<h1>Hello, {props.name}!</h1>
		<p>Good to see you</p>
	  </div>
	);
  }`;

const codeLua = `local b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
local decode_table = ffi.new 'uint8_t[256]'
for i = 1, #b64 do
  decode_table[str_byte(b64, i)] = i - 1 -- Base64 values start from 0
end

function BloomFilter:has(key)
  local ptr = self.ptr -- uint8_t* pointer to start of base64 string
  for byte_offset, bit_offset in self:iterator(key) do
    local sextet = decode_table[ptr[byte_offset]]
    if band(sextet, lshift(1, bit_offset)) == 0 then
      return false
    end
  end
  return true
end`;

const languages = [
  {
    label: 'JavaScript',
    value: 'js',
  },
  {
    label: 'TypeScript',
    value: 'ts',
  },
  {
    label: 'Next.js',
    value: 'next',
  },
  {
    label: 'Lua',
    value: 'lua',
  },
];

export function Component(): JSX.Element {
  const [language, setLanguage] = useState('js');

  if (language === 'ts') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="language-switcher.tsx"
        language="tsx"
        tabs={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeTs}
      </CodeBlock>
    );
  }

  if (language === 'next') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="language-switcher.tsx"
        language="next"
        tabs={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeTs}
      </CodeBlock>
    );
  }

  if (language === 'lua') {
    return (
      <CodeBlock
        aria-label="Hello world"
        filename="bloom-filter.lua"
        language="lua"
        tabs={{
          options: languages,
          value: language,
          onChange: (l) => setLanguage(l),
        }}
      >
        {codeLua}
      </CodeBlock>
    );
  }

  return (
    <CodeBlock
      aria-label="Hello world"
      filename="language-switcher.jsx"
      language="jsx"
      tabs={{
        options: languages,
        value: language,
        onChange: (l) => setLanguage(l),
      }}
    >
      {code}
    </CodeBlock>
  );
}
```

## Hidden line numbers

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `function MyComponent(props) {
	return (
	  <div>
		<h1>Hello, {props.name}!</h1>
		<p>Good to see you</p>
	  </div>
	);
  }`;

export function Component(): JSX.Element {
  return (
    <CodeBlock
      aria-label="Hello world"
      filename="hidden-line-numbers.jsx"
      hideLineNumbers
      language="jsx"
    >
      {code}
    </CodeBlock>
  );
}
```

## Open in v0

Use the `v0` prop to add an Open in v0 action to the toolbar.

```tsx
import { CodeBlock } from '@vercel/geistcn/components';
import type { JSX } from 'react';

const code = `function MyComponent(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>This is an example React component.</p>
    </div>
  );
}`;

export function Component(): JSX.Element {
  return (
    <div className="flex flex-col gap-4">
      <CodeBlock
        aria-label="Hello world"
        filename="Table.jsx"
        language="jsx"
        v0="ask"
      >
        {code}
      </CodeBlock>
      <CodeBlock
        aria-label="Hello world"
        filename="Table.jsx"
        language="jsx"
        v0="build"
      >
        {code}
      </CodeBlock>
    </div>
  );
}
```

## Best Practices

### When to use

* Use `<CodeBlock>` for multi-line, syntax-highlighted source the reader will scan or paste.
* For a single inline token (env var, function name, file path), use `Inline Code`.
* Pick `Snippet` for a copy-to-clipboard shell command or one-line key reveal; it ships with the prompt glyph and copy affordance built in.

### Behavior

* Always pass a `language`/`syntax` (`tsx`, `bash`, `json`, `diff`). Highlighting is the primary reason to choose `<CodeBlock>` over a plain `<pre>`.
* Highlight only the lines under discussion. A block where every line is highlighted reads the same as no highlight at all.
* Mark added or removed lines with the `diff` syntax or the dedicated added/removed props. Faking them with `// added` comments breaks copy-paste.
* Show the filename header when the snippet has a paste destination (`app/page.tsx`, `vercel.json`). Omit it for ephemeral examples.

### Content

* Snippets stay runnable. Don’t paraphrase real code into pseudo-syntax, and don’t prepend `$` to shell commands. `<Snippet>` already renders the prompt, so `text="$ vercel deploy"` doubles to `$ $ vercel deploy`.
* Keep prose around the block in sentence case and put backticks around CLI flags inline (`` `--prebuilt` ``).
