CodeBlock
DocsKit renders syntax-highlighted code blocks using Shiki, a server-side highlighter that produces accurate, token-level coloring for over 100 languages. Code blocks support an optional filename tab and include a one-click copy button.
Basic usage
Write fenced code blocks in your MDX exactly as you would in standard Markdown. The language identifier after the opening fence tells Shiki which grammar to apply:
```typescript
function greet(name: string): string {
return `Hello, ${name}!`;
}
```Renders as:
function greet(name: string): string {
return `Hello, ${name}!`;
}Filename tabs
Add a filename tab by including filename="..." in the opening fence. This is particularly useful in tutorials where readers need to know which file to edit:
```ts filename="src/lib/client.ts"
import { createClient } from "@acme/sdk";
export const client = createClient({
apiKey: process.env.ACME_API_KEY!,
baseUrl: "https://api.acme.dev",
});
```Renders as:
import { createClient } from "@acme/sdk";
export const client = createClient({
apiKey: process.env.ACME_API_KEY!,
baseUrl: "https://api.acme.dev",
});The filename appears in a tab above the code. The tab is purely decorative - it does not make the block interactive or editable.
Copy button
Every code block includes a copy button in the top-right corner. It copies the raw code text (without the filename or syntax highlighting markup) to the clipboard. The button fades to visible on hover and shows a checkmark briefly after a successful copy.
The copy button is rendered client-side and does not appear in the static HTML - it's injected via a small script after page load. This means it works correctly in static export mode.
Supported languages
Shiki supports 100+ languages. The most commonly used ones in technical documentation:
| Language | Identifier | Example use |
|---|---|---|
| TypeScript | ts or typescript | SDK examples, type definitions |
| JavaScript | js or javascript | Browser scripts, Node.js |
| TSX | tsx | React components with TypeScript |
| JSX | jsx | React components with JavaScript |
| Bash / Shell | bash or sh | CLI commands, scripts |
| JSON | json | Configuration files, API responses |
| CSS | css | Stylesheet examples |
| HTML | html | Markup examples |
| MDX | mdx | DocsKit content examples |
| Python | python or py | Backend examples |
| Go | go | Server-side examples |
| Rust | rust | Systems programming examples |
| SQL | sql | Database queries |
| YAML | yaml or yml | CI/CD config, Docker Compose |
If you use a language identifier that Shiki doesn't recognize, it will render the block as plain text without an error.
Use bash rather than shell or sh for terminal commands - Shiki's bash grammar handles common patterns like flags, pipes, and environment variables the best.
Real-world example
Here's a more complete example showing a TypeScript file with multiple imports, types, and async functions - the kind of snippet that appears in real API integration guides:
import { type IncomingMessage, type ServerResponse } from "http";
import crypto from "crypto";
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
interface WebhookPayload {
event: "project.created" | "project.deleted" | "deploy.succeeded";
timestamp: number;
data: Record<string, unknown>;
}
function verifySignature(body: string, signature: string): boolean {
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(body, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
export async function handleWebhook(
req: IncomingMessage,
res: ServerResponse
): Promise<void> {
const signature = req.headers["x-acme-signature"] as string;
const body = await readBody(req);
if (!verifySignature(body, signature)) {
res.writeHead(401);
res.end("Unauthorized");
return;
}
const payload: WebhookPayload = JSON.parse(body);
console.log(`Received event: ${payload.event}`);
res.writeHead(200);
res.end("OK");
}
async function readBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
req.on("error", reject);
});
}Changing the syntax theme
The default theme is vesper. To switch themes, update the theme option in src/lib/mdx.ts. See Configuration Options - Syntax highlighting themes for the full list of available themes.