Syntax Highlighters
CodeGloss ships a small built-in regex tokenizer that handles the common JS/TS cases well enough for demos. For production docs, plug in a real highlighter — Shiki, Prism, or highlight.js — via a tiny adapter. You bring the library, codegloss turns its HTML into annotated lines.
How it works
A Highlighter is just a function:
type Highlighter = (
code: string,
lang: string,
) => string | { html: string; background?: string; color?: string };
Return the tokenized HTML for the whole code block (no outer <pre> / <code> wrappers) — codegloss splits it into lines, preserves open spans across newlines, and weaves your annotation <mark>s into the tokenized output. Anything that emits span-wrapped tokens works: Shiki, Prism, highlight.js, Starry Night, or your own.
Codegloss stays syntax-agnostic. Colors come from your highlighter, not from codegloss. When the highlighter reports its chrome (via the { background, color } return) codegloss applies those as --cg-bg and --cg-text on the host so the block matches the highlighter's theme. No named codegloss theme required.
Declare it once in codegloss.config.ts
The config carries a highlight field. Set it once and every render path picks it up — the remark plugin reads it at build time, server pages can call it directly for hand-written blocks, and a single initCodegloss(config) call wires it into the runtime web component for dynamic / interactive pages.
// codegloss.config.ts
import { defineConfig } from 'codegloss/config';
import { createShikiHighlighter } from 'codegloss/highlighters/shiki';
import { createHighlighter } from 'shiki';
const SHIKI_THEME = 'github-dark';
// Optional: also export the Shiki instance so non-codegloss fenced blocks
// (rehype-shiki) and codegloss can share one tokenizer — same theme, same
// langs, no drift between the two render paths.
export const shiki = await createHighlighter({
themes: [SHIKI_THEME],
langs: ['js', 'ts', 'tsx', 'py', 'rust'],
});
export default defineConfig({
theme: SHIKI_THEME,
highlight: createShikiHighlighter(shiki, { theme: SHIKI_THEME }),
});
Prism and highlight.js are sync — no top-level await, no shared instance dance:
// codegloss.config.ts
import { defineConfig } from 'codegloss/config';
import { createPrismHighlighter } from 'codegloss/highlighters/prism';
import Prism from 'prismjs';
import 'prismjs/components/prism-typescript';
export default defineConfig({
highlight: createPrismHighlighter(Prism, { theme: 'tomorrow' }),
});
Three places it gets consumed
The same Highlighter function works in every consumer pattern. Pick the one(s) that match where your code lives — they're all the same shape.
Build time — the remark plugin
For MDX/markdown, hand the highlighter to the remark plugin (typically by spreading the config). Each fenced codegloss block is pre-highlighted at build, baked into the emitted config, and rendered straight from HTML — no client-side highlighter download, no flicker.
import remarkCodegloss from 'codegloss/remark';
import codeglossConfig from './codegloss.config';
remarkPlugins: [[remarkCodegloss, { highlight: codeglossConfig.highlight }]];
Render time — the framework wrapper highlight prop
<CodeGloss> accepts a highlight prop on every wrapper. The wrapper invokes it during render, drops the resulting HTML into the config, and the element renders pre-highlighted markup. Use this for handwritten React/Vue/Svelte blocks that don't go through remark — e.g. a server component pre-rendering the homepage hero.
import { CodeGloss } from '@codegloss/react';
import codeglossConfig from '@/codegloss.config';
<CodeGloss code="..." lang="js" highlight={codeglossConfig.highlight} />
Runtime — initCodegloss(config)
For dynamic blocks where the code isn't known at build time (a code editor, a live preview), call initCodegloss(config) once near the root of your client app. It registers config.highlight as the default for every <code-gloss> instance — current and future — and refreshes mounted blocks so async highlighters (Shiki) take effect without a reload.
// app root (client)
import { initCodegloss } from 'codegloss';
import codeglossConfig from './codegloss.config';
initCodegloss(codeglossConfig);
If you need finer control (e.g. you'd rather not bundle your highlighter with every client page that touches the config), you can still use the lower-level setDefaultHighlighter(fn) directly.
Pick your highlighter
TextMate-grade highlighting with real editor themes. Create a Shiki instance once (async — it loads grammars and themes), then register the adapter. Dual light/dark theming supported via the themes option. Because createHighlighter is async, blocks rendered before Shiki initializes briefly use the built-in regex tokenizer; setDefaultHighlighter refreshes them in place as soon as the swap happens.
npm install codegloss shikiimport { setDefaultHighlighter } from 'codegloss';
import { createShikiHighlighter } from 'codegloss/highlighters/shiki';
import { createHighlighter } from 'shiki';
const shiki = await createHighlighter({
themes: ['github-dark'],
langs: ['js', 'ts', 'tsx', 'py', 'rust'],
});
setDefaultHighlighter(
createShikiHighlighter(shiki, { theme: 'github-dark' }),
);Bundle cost
Each adapter is ~100 B of glue; the library itself (Shiki / Prism / hljs) is declared as an optional peer dependency so you only install the one you're using. A codegloss user who sticks with the built-in tokenizer pays nothing extra. If you only render codegloss blocks via the remark plugin (no client interactivity), the highlighter never reaches the browser — everything is baked into HTML at build time.