TanStack logo

Skill

theme-and-annotate-code

style and annotate code blocks

Covers CSS Themes Frontend

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.

SKILL.md

Theme and Annotate Code

Setup

Generate light and dark CSS independently from highlighted markup:

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

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

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

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

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

.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:

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:

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:

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:

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:

import type { HighlightTheme } from '@tanstack/highlight/theme'

export const theme = {
  name: 'docs',
  type: 'light',
  background: '#ffffff',
  foreground: '#24292f',
  tokens: {
    keyword: '#cf222e',
  },
} as HighlightTheme

Correct:

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:

import { highlighter } from './highlight'

export const result = highlighter.highlight('first\nsecond\nthird', {
  decorations: [{ lines: [0, 2], className: 'is-focused' }],
})

Correct:

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:

import { highlighter } from './highlight'

export const result = highlighter.highlight('const value = 1', {
  lang: 'ts',
  decorations: [{ lines: 1, className: 'th-line--highlighted' }],
})

Correct:

.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

See also: integrate-markdown-pipelines/SKILL.md - fence metadata is the standard source for documentation line annotations.

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.