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.
Related
- Quality Checks — lint, type-check, and test the code blocks themselves.
- Code Annotations example
- Code Imports example