CodeBlock

Code blocks in DocsKit Free render as clean, readable monospace text with a filename tab and a one-click copy button. What they don't have is per-token syntax coloring - that's a DocsKit Pro feature (Shiki, 100+ languages).

Basic usage

Write fenced code blocks in your MDX exactly as you would in standard Markdown:

```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) 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.

Need color-coded syntax for 100+ languages? DocsKit Pro adds Shiki highlighting to the exact same CodeBlock component - no migration needed if you upgrade later, since the MDX syntax is identical.

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);
  });
}