Code Block

Code Block

Syntax-highlighted code blocks powered by rehype-pretty-code and Shiki

Installation

npx @ravikumarsurya/mdx-ui add code-block

Then set up syntax highlighting with rehype-pretty-code:

npm install rehype-pretty-code shiki

Setup

Next.js (next.config.ts)

import createMDX from "@next/mdx";
import rehypePrettyCode from "rehype-pretty-code";
 
const withMDX = createMDX({
  options: {
    rehypePlugins: [
      [
        rehypePrettyCode,
        {
          theme: { light: "github-light-default", dark: "github-dark-default" },
          defaultColor: false,
        },
      ],
    ],
  },
});
 
export default withMDX({ pageExtensions: ["ts", "tsx", "md", "mdx"] });

next-mdx-remote

import { compileMDX } from "next-mdx-remote/rsc";
import rehypePrettyCode from "rehype-pretty-code";
 
const { content } = await compileMDX({
  source,
  options: {
    mdxOptions: {
      rehypePlugins: [
        [
          rehypePrettyCode,
          {
            theme: {
              light: "github-light-default",
              dark: "github-dark-default",
            },
            defaultColor: false,
          },
        ],
      ],
    },
  },
});

Contentlayer / Contentlayer2

// contentlayer.config.ts
import rehypePrettyCode from "rehype-pretty-code";
 
export default makeSource({
  mdxOptions: {
    rehypePlugins: [
      [
        rehypePrettyCode,
        {
          theme: { light: "github-light-default", dark: "github-dark-default" },
          defaultColor: false,
        },
      ],
    ],
  },
});

Map pre to CodeBlock

In your MDX components file, map the pre element to CodeBlock:

import { CodeBlock } from "@/components/mdx/code-block";
 
const components = {
  pre: CodeBlock,
  // ... other components
};

Usage

components/mdx/badge.tsx
import { Badge } from "@/components/mdx/badge"

export function StatusBadge({ status }: { status: string }) {
  return <Badge variant="success">{status}</Badge>
}

Once configured, fenced code blocks in MDX get full syntax highlighting automatically:

```tsx title="button.tsx"
export function Button({ children }: { children: React.ReactNode }) {
  return (
    <button className="px-4 py-2 bg-primary text-primary-foreground rounded-md">
      {children}
    </button>
  );
}
```

With a title

Annotate the code fence with title="...":

```bash title="Install dependencies"
npm install rehype-pretty-code shiki
```

Line highlighting

rehype-pretty-code supports line and word highlighting out of the box:

```tsx {2,4-6}
function greet(name: string) {
  const greeting = `Hello, ${name}!`; // highlighted
  console.log(greeting);
  return {
    // highlighted
    message: greeting, // highlighted
    timestamp: Date.now(), // highlighted
  };
}
```

Word highlighting

```tsx /useState/
const [count, setCount] = useState(0);
const [name, setName] = useState("");
```

How it works

The CodeBlock component renders as the <pre> element in MDX. When rehype-pretty-code processes your MDX:

  1. It reads the language and title from your code fence annotation
  2. Runs Shiki to tokenize and highlight the code
  3. Passes data-language and data-title to the <pre> element
  4. CodeBlock reads those attributes to show the language badge and title

The CSS for Shiki token colors is automatically injected into your globals.css by npx @ravikumarsurya/mdx-ui init:

/* Shiki dual-theme — injected by mdx-ui init */
[data-code-block] code span {
  color: var(--shiki-light);
  font-style: var(--shiki-light-font-style);
  font-weight: var(--shiki-light-font-weight);
}
.dark [data-code-block] code span {
  color: var(--shiki-dark);
  font-style: var(--shiki-dark-font-style);
  font-weight: var(--shiki-dark-font-weight);
}

Props

PropTypeDefaultDescription
titlestring-Override the title shown in the header
showLineNumbersbooleanfalseEnable CSS counter-based line numbers
data-languagestring-Set by rehype-pretty-code; shown as language badge
data-titlestring-Set by rehype-pretty-code from title annotation
classNamestring-Additional classes on the <pre> element

Without rehype-pretty-code

The component still works without a syntax highlighting plugin — code renders with the correct monospace font and copy button, just without token colors.