@tanstack/highlight
Version:
Tiny class-based syntax highlighting for documentation.
308 lines (230 loc) • 7.25 kB
Markdown
---
name: 'theme-and-annotate-code'
description: >
Style @tanstack/highlight with createThemeCss, createThemeRule,
createThemeBaseCss, isolated theme imports, lineNumbers, line decorations,
UTF-16 range decorations, and fence annotations. Load for light/dark themes,
custom semantic colors, focused lines, diffs, diagnostics, or data hooks.
metadata:
type: core
library: '@tanstack/highlight'
library_version: '1.0.0'
sources:
- 'TanStack/highlight:docs/guides/themes.md'
- 'TanStack/highlight:docs/guides/annotations.md'
- 'TanStack/highlight:docs/guides/output-and-css.md'
- 'TanStack/highlight:src/theme.ts'
- 'TanStack/highlight:src/core.ts'
---
# Theme and Annotate Code
## Setup
Generate light and dark CSS independently from highlighted markup:
```ts
import { createThemeCss } from '@tanstack/highlight/theme'
import { githubDarkTheme } from '@tanstack/highlight/themes/github-dark'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
export const syntaxThemeCss = createThemeCss({
light: githubLightTheme,
dark: githubDarkTheme,
lightSelector: ':root',
darkSelector: '.dark',
})
```
The default selectors are `:root` and `.dark`; base code and token styles are included unless `includeBaseStyles` is false.
## Core Patterns
### Select among named themes
```ts
import { createThemeCss } from '@tanstack/highlight/theme'
import { draculaTheme } from '@tanstack/highlight/themes/dracula'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
export const syntaxThemeCss = createThemeCss({
themes: [
{
selector: '[data-code-theme="github"]',
theme: githubLightTheme,
},
{
selector: '[data-code-theme="dracula"]',
theme: draculaTheme,
},
],
})
```
`themes` mode is mutually exclusive with the `light` and `dark` pair mode.
### Target renderer-owned code blocks
```ts
export const syntaxThemeCss = createThemeCss({
light: githubLightTheme,
dark: githubDarkTheme,
lightSelector: '.markdown-renderer',
darkSelector: '.dark .markdown-renderer',
codeBlockSelector: '.markdown-renderer pre.tm-code',
lineNumbersSelector: '.markdown-renderer .tm-code--line-numbers',
})
```
The selector defaults are `pre.th-code` and `.th-code--line-numbers`. Override both when the surrounding renderer owns different wrapper classes.
### Add line numbers and line decorations
```ts
import { highlighter } from './highlight'
export const result = highlighter.highlight(
'const first = 1\nconst second = 2\nconst third = 3',
{
lang: 'ts',
lineNumbers: true,
decorations: [
{ lines: 2, className: 'is-focused' },
{ lines: [2, 3], className: 'is-highlighted' },
],
},
)
```
Line coordinates are one-based and inclusive; decorations activate `th-line` wrappers even without line numbers.
### Attach exact source diagnostics
```ts
import { highlighter } from './highlight'
const code = 'const answer = unknownValue'
const start = code.indexOf('unknownValue')
export const result = highlighter.highlight(code, {
lang: 'ts',
decorations: [
{
range: [start, start + 'unknownValue'.length],
className: 'is-error',
data: {
message: 'Unknown identifier',
severity: 2,
},
},
],
})
```
Character ranges use zero-based, end-exclusive UTF-16 offsets.
### Own font styles and annotation presentation in CSS
```css
.th-keyword {
font-weight: 600;
}
.th-comment {
font-style: italic;
}
.th-line--highlighted {
background: rgb(9 105 218 / 10%);
}
.is-error {
text-decoration: underline wavy #cf222e;
}
```
Theme objects contain colors only; typography, contrast policy, and annotation appearance belong to application CSS.
## Common Mistakes
### HIGH Duplicating markup for light and dark
Wrong:
```ts
import { highlighter } from './highlight'
export const lightHtml = highlighter.highlightToHtml('const value = 1', {
lang: 'ts',
})
export const darkHtml = highlighter.highlightToHtml('const value = 1', {
lang: 'ts',
})
```
Correct:
```ts
import { createThemeCss } from '@tanstack/highlight/theme'
import { githubDarkTheme } from '@tanstack/highlight/themes/github-dark'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
import { highlighter } from './highlight'
export const html = highlighter.highlightToHtml('const value = 1', {
lang: 'ts',
})
export const css = createThemeCss({
light: githubLightTheme,
dark: githubDarkTheme,
})
```
Token classes are theme-independent, so CSS can switch one highlighted tree.
Source: `docs/guides/themes.md`
### MEDIUM Retaining every candidate theme
Wrong:
```ts
import { auroraXTheme } from '@tanstack/highlight/themes/aurora-x'
import { draculaTheme } from '@tanstack/highlight/themes/dracula'
import { monokaiTheme } from '@tanstack/highlight/themes/monokai'
export const selectedTheme = {
aurora: auroraXTheme,
dracula: draculaTheme,
monokai: monokaiTheme,
}.dracula
```
Correct:
```ts
import { draculaTheme } from '@tanstack/highlight/themes/dracula'
export const selectedTheme = draculaTheme
```
Direct subpaths isolate themes only when unused theme modules are not imported.
Source: `docs/guides/themes.md`
### HIGH Hiding an incomplete custom theme
Wrong:
```ts
import type { HighlightTheme } from '@tanstack/highlight/theme'
export const theme = {
name: 'docs',
type: 'light',
background: '#ffffff',
foreground: '#24292f',
tokens: {
keyword: '#cf222e',
},
} as HighlightTheme
```
Correct:
```ts
import type { HighlightTheme } from '@tanstack/highlight/theme'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
export const theme = {
...githubLightTheme,
name: 'docs',
tokens: {
...githubLightTheme.tokens,
keyword: '#cf222e',
},
} satisfies HighlightTheme
```
Bypassing the complete token record emits missing CSS variables for semantic classes.
Source: `docs/guides/themes.md`
### HIGH Using character coordinates for lines
Wrong:
```ts
import { highlighter } from './highlight'
export const result = highlighter.highlight('first\nsecond\nthird', {
decorations: [{ lines: [0, 2], className: 'is-focused' }],
})
```
Correct:
```ts
import { highlighter } from './highlight'
export const result = highlighter.highlight('first\nsecond\nthird', {
decorations: [{ lines: [1, 3], className: 'is-focused' }],
})
```
Line ranges are one-based and inclusive, unlike zero-based character ranges.
Source: `docs/guides/annotations.md`
### MEDIUM Assuming annotations include appearance
Wrong:
```ts
import { highlighter } from './highlight'
export const result = highlighter.highlight('const value = 1', {
lang: 'ts',
decorations: [{ lines: 1, className: 'th-line--highlighted' }],
})
```
Correct:
```css
.th-line--highlighted {
background: rgb(9 105 218 / 10%);
}
```
The renderer emits classes and escaped data attributes but does not style annotation classes.
Source: `docs/guides/annotations.md`
## References
- [Theme matrix, semantic tokens, and complete custom themes](references/themes-and-tokens.md)
See also: `integrate-markdown-pipelines/SKILL.md` - fence metadata is the standard source for documentation line annotations.