What you get for free
Any fenced code block in your MDX gets three things automatically — no imports, no wrapper components, no config.
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
Data
Web
Systems
Scripting
Other
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 resultsCombining 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.