Code Blocks

Three opt-in features extend fenced code blocks: Shiki-based syntax highlighting, annotation syntax for highlighting and diff markers, and importing snippets from real source files. This site enables all three, so every example below is rendered live.

Option Type Default
highlight boolean false
highlightTheme string / ThemeRegistration "github-dark"
highlightLangs LanguageRegistration[] []
codeAnnotations boolean / CodeAnnotationsOptions false
codeImports boolean / CodeImportOptions false

Syntax Highlighting

Highlighting is opt-in because it adds Shiki to the build:

import { oxContent } from "@ox-content/vite-plugin";

export default {
  plugins: [
    oxContent({
      highlight: true,
      // Optional: a Shiki theme name or a full theme registration object.
      highlightTheme: "github-dark",
      // Optional: extra TextMate grammars for custom languages.
      highlightLangs: [],
    }),
  ],
};

Every code block on this site is highlighted through this pipeline — this page uses a custom highlightTheme registration to match the site design. After highlighting, code block metadata (annotations, line numbers) is merged back into Shiki's output natively.

Code Annotations

Annotations are opt-in so ordinary fences stay literal unless a site chooses an annotation syntax:

oxContent({
  highlight: true,
  codeAnnotations: {
    // "attribute" (default) | "vitepress" | "both"
    notation: "both",
    // Attribute name used by the attribute syntax. Default: "annotate".
    metaKey: "annotate",
    // Render line numbers for every block. Default: false.
    defaultLineNumbers: false,
  },
});

Supported annotation kinds are highlight, warning, and error.

Attribute notation

The default notation is a single fence attribute with kind:lines groups separated by ;. Line selectors accept single lines (5) and ranges (3-4):

```ts annotate="highlight:1,6;warning:2;error:3"
export function loadUser(input: string) {
  if (!input) console.warn("missing payload");
  throw new Error("missing id");
}

const user = loadUser(payload);
console.log(user);
```

Rendered:

export function loadUser(input: string) {
  if (!input) console.warn("missing payload");
  throw new Error("missing id");
}

const user = loadUser(payload);
console.log(user);

VitePress notation

notation: "vitepress" (or "both") enables VitePress-compatible fence metadata and inline comment directives. The fence meta pieces compose independently:

  • {1,3} — highlighted lines.
  • [config.ts] — a filename label rendered above the block.
  • :line-numbers / :line-numbers=7 / :no-line-numbers — line numbers per block, with an optional start.
```ts:line-numbers=7 {1,3} [config.ts]
const token = readToken();
const expires = readExpiry(token);
refreshBefore(expires);
```

Rendered:

const token = readToken();
const expires = readExpiry(token);
refreshBefore(expires);

Inline comment directives annotate the line they sit on and are removed from the output. This block is authored with // [!code warning] on the second line and // [!code error] on the third:

const token = readToken();
console.warn("Token expires soon");
throw new Error("Token is invalid");

Diff notation uses // [!code --] for removed and // [!code ++] for added lines — this block carries them on the two return lines:

export function resolve(id: string) {
  return legacyResolve(id);
  return nativeResolve(id);
}

// [!code focus] (or // [!code focus:3] for a range) dims everything but the focused lines.

Inline directives are consumed wherever they appear inside a code block — including fence examples nested in an outer fence — so use the escape directive below when a line needs to show annotation-looking text.

Escaping

A standalone // [!code escape] comment is removed from the output and makes the next line render literally. This block is authored with an escape comment above the first console.warn line, so its // [!code warning] survives as text while the second one becomes an annotation:

console.warn("literal"); // [!code warning]
console.warn("annotated");

Custom meta key

Swap annotate for a more domain-specific attribute name:

oxContent({
  codeAnnotations: {
    metaKey: "markers",
  },
});
```ts markers="highlight:2;warning:3"
const token = readToken();
refreshToken(token);
console.warn("Token expires soon");
```

Code Imports

Import checked source files into Markdown instead of copy-pasting them:

oxContent({
  codeImports: {
    // Root for `@/` imports. Defaults to the Vite project root.
    rootDir: process.cwd(),
  },
});

The fence language is inferred from the file extension, and imported snippets go through the same highlighting and annotation pipeline as inline fences.

Writing <<< @/snippets/greet.ts on its own line imports the whole file:

export interface Greeting {
  name: string;
  message: string;
}

// #region greet
export function greet(name: string): Greeting {
  return {
    name,
    message: `Hello, ${name}!`,
  };
}
// #endregion greet

export function farewell(name: string): string {
  return `Goodbye, ${name}.`;
}

A {1-4} suffix — <<< @/snippets/greet.ts{1-4} — imports a line range:

export interface Greeting {
  name: string;
  message: string;
}

A named suffix — <<< @/snippets/greet.ts{greet} — imports the region delimited by #region greet / #endregion greet comments, with the markers themselves stripped:

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

Because imports resolve at transform time, editing the source file updates every page that imports it, and stale docs snippets stop being possible.

<<< references are resolved inside fenced code blocks too, so quote the syntax with inline code (as this page does) when you need to show it literally.