Skip to main content

Features

Code Blocks

Syntax highlighting, line numbers, highlighting, focus, word wrap, expandable sections, and copy buttons on every fenced code block.

What you get for free

Any fenced code block in your MDX gets three things automatically — no imports, no wrapper components, no config.

Syntax highlighting

Theme-aware colors via Prism, scoped to each tenant's --docs-* tokens.

Copy button

Hover-revealed copy button in the top-end corner. Success state for 1.8 seconds.

Language badge

Small monospace label in the top-start corner showing the language name.

Writing code blocks

Standard markdown fences with a language tag. The language tag drives both the syntax highlighter and the badge.

```typescript
export function greet(name: string): string {
  return `Hello, ${name}!`;
}
```

Produces:

export function greet(name: string): string {
  return `Hello, ${name}!`;
}

Supported languages

The Prism grammar set is loaded client-side on docs sites. Current set:

Shell

bash

Data

json, yaml

Web

typescript, tsx, jsx, css

Systems

go, rust, java

Scripting

python

Other

sql, markdown

Need another language? Open an issue on GitHub — Prism has grammars for most popular languages and adding one is a single import line.

Grouping multiple languages

Use <CodeGroup> to show the same code in different languages with tabs. See the CodeGroup component docs.

Theme integration

Syntax colors are not hard-coded. Prism token classes (.token.keyword, .token.string, etc.) are mapped to --docs-* CSS variables in prose-docs, so the highlighting:

  • Automatically adapts to the reader's light/dark theme choice

  • Uses your tenant primary color for keywords

  • Uses your configured success/warning colors for strings and numbers

  • Stays subtle (Linear-style muted hues, not a "christmas tree" explosion)

Line numbers

Add lines after the language tag to show line numbers in the gutter.

```typescript lines
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");
```

Result:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");

Highlighting lines

Use highlight={lines} to visually emphasize specific lines. Accepts single numbers and ranges.

```typescript highlight={1,4-6}
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");
```

Result:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");

Highlighted lines get a tinted background with a left border accent using your theme's primary color.

Focusing lines

Use focus={lines} to draw attention to specific lines while dimming the rest. On hover, dimmed lines become more readable.

```typescript focus={4-6}
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");
```

Result:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data } = await supabase.from("pages").select("*");

Word wrapping

Add wrap to enable word wrapping for lines that exceed the code block width.

```bash wrap
curl -X POST https://api.example.com/v1/documents/create -H "Authorization: Bearer sk-1234567890abcdef" -H "Content-Type: application/json" -d '{"title": "Getting Started", "content": "This is a very long line."}'
```

Result:

curl -X POST https://api.example.com/v1/documents/create -H "Authorization: Bearer sk-1234567890abcdef" -H "Content-Type: application/json" -d '{"title": "Getting Started", "content": "This is a very long line that would normally overflow the code block horizontally."}'

Expandable code

Add expandable to collapse long code blocks (more than 15 lines). A "Show more" button appears at the bottom.

```python expandable
import os
from pathlib import Path

def process(directory):
    results = []
    ...
```

Result:

import os
import json
from pathlib import Path

def process_documents(directory: str) -> list[dict]:
    """Walk a directory and process all markdown files."""
    results = []
    base = Path(directory)

    for path in sorted(base.rglob("*.md")):
        with open(path) as f:
            content = f.read()

        meta = extract_frontmatter(content)
        body = strip_frontmatter(content)

        results.append({
            "path": str(path.relative_to(base)),
            "title": meta.get("title", path.stem),
            "content": body,
            "word_count": len(body.split()),
        })

    return results

Combining options

Meta options can be combined freely. The order does not matter.

```typescript lines highlight={3-5}
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);
```

Result:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

Title / filename

Any text in the meta string that is not a recognized option becomes the code block's header title. This is how filenames work.

```typescript src/lib/db.ts
export const db = createClient(url, key);
```

Result:

export const db = createClient(url, key);

Copy button behavior

The copy button sits in the top-end corner of every code block. It is hidden until the reader hovers over the block (or focuses it via keyboard), then reveals. Clicking copies the raw code (without the language tag or badge) to the system clipboard via the Clipboard API. On success the icon swaps to a checkmark for ~2 seconds.

The button relies on navigator.clipboard.writeText, which requires HTTPS or localhost. If the Clipboard API is blocked by the browser (very rare), the button silently fails — the code still renders normally.

Was this page helpful?

Last updated August 7, 2026