Remark Plugin
remark-codegloss is a unified remark plugin that transforms annotated fenced code blocks into CodeGloss instances at build time.
It supports two output modes:
mdx(default) — emits<CodeGloss />MDX JSX. Use with MDX pipelines:@next/mdx,next-mdx-remote, Velite, Docusaurus.html— emits a raw<code-gloss>HTML node with the config in a<script type="application/json">child. Use with plain markdown pipelines (remark-rehype→rehype-stringify) or anywhere outside of MDX.
How it works
The plugin walks the markdown AST looking for a specific pattern:
- A fenced code block with
codeglossin its metadata - Optionally followed by a
json annotationscode block
When it finds a match, it:
- Replaces both blocks with a single CodeGloss node (JSX or HTML, depending on
outputmode) - Parses the annotations JSON and forwards its top-level keys (
annotations,connections,arcs,callouts) as individual props — each key becomes its own prop on the emitted node, not merged - In
mdxmode, auto-injectsimport { CodeGloss } from '@codegloss/react'(skippable viaskipImport)
Detection pattern
The code block metadata must match this format:
lang(required) — the language identifier (js,ts,py,css, etc.)codegloss(required) — the keyword that triggers detectionfilename(optional) — displayed in the toolbar
Annotations block
The annotations block must immediately follow the codegloss block:
Four top-level keys are recognized:
annotations— array ofAnnotationobjects. Each annotation can set"popover": trueto switch its callout from inline-expanding to floating-popover presentation, and"defaultOpen": trueto pre-open it on first render.connections— array ofConnectionobjects. Each connection can set"side": "right"to render on the right side of the block instead of in the left gutter, and"defaultOpen": trueto pre-open its popover on first render.arcs— arc-style overrides forwarded verbatim as a prop. Most commonly used for"arrowhead": trueto turn on arrowheads at thetoendpoint. See Arc style options for the full field list.callouts— block-level callout behavior overrides. Set"popover": trueto make every annotation in the block open as a floating popover. See Callout options.
See Pre-opening callouts for the full rules around defaultOpen — at most one annotation + one connection, last-wins cascade when multiple are marked.
If no annotations block follows, the component renders the code without annotations.
Options
type RemarkCodeglossOptions = {
/**
* Output mode. Default: 'mdx'.
* - 'mdx': emits CodeGloss MDX JSX (use with MDX pipelines)
* - 'html': emits a raw <code-gloss> HTML node (use with plain markdown pipelines)
*/
output?: 'mdx' | 'html';
/**
* (mdx mode only) Skip injecting `import { CodeGloss } from '@codegloss/react'`.
* Set true when CodeGloss is provided via a custom MDX components map.
*/
skipImport?: boolean;
/**
* Default theme applied to all code blocks unless the block specifies its own.
* Accepts a bundled theme name (e.g. 'github-dark', 'dracula').
*/
theme?: string;
};
Applying a global theme
Pass a theme option to apply a default theme to all code blocks processed by the plugin:
remarkCodegloss({ theme: 'github-dark' })
Individual code blocks can override the global default — the remark plugin only injects the theme when the block doesn't specify its own.
Configuration examples
MDX mode (default)
Next.js with @next/mdx
import createMdx from '@next/mdx';
import remarkCodegloss from 'codegloss/remark';
const withMdx = createMdx({
options: {
remarkPlugins: [remarkCodegloss],
},
});
Docusaurus
import remarkCodegloss from 'codegloss/remark';
export default {
presets: [
['classic', {
docs: {
remarkPlugins: [[remarkCodegloss, { skipImport: true }]],
},
}],
],
};
skipImport: true is recommended in Docusaurus because the component is provided via the swizzled MDX components map (see the Docusaurus setup guide).
Non-React MDX (Vue, Preact)
The auto-injected import targets @codegloss/react, which matches the dominant @mdx-js/react consumers (Next.js, Docusaurus, Velite, Astro's MDX integration). If you're using @mdx-js/vue or @mdx-js/preact, set skipImport: true and provide CodeGloss via your MDX components mapping — point it at @codegloss/vue or your own wrapper:
// mdx config
remarkPlugins: [[remarkCodegloss, { skipImport: true }]],
// MDX components mapping
import { CodeGloss } from '@codegloss/vue';
export const components = { CodeGloss };
For SvelteKit (mdsvex) and VitePress, prefer output: 'html' instead — it emits a raw <code-gloss> element with no import at all.
Generic MDX pipeline
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkMdx from 'remark-mdx';
import remarkCodegloss from 'codegloss/remark';
const processor = unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkCodegloss);
HTML mode (plain markdown)
For pipelines that produce plain HTML (no JSX, no React):
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import remarkCodegloss from 'codegloss/remark';
const html = await unified()
.use(remarkParse)
.use(remarkCodegloss, { output: 'html' })
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdownSource);
The output is a raw <code-gloss> element with the config in a <script type="application/json"> child:
<code-gloss><script type="application/json">{"code":"...","lang":"js",...}</script></code-gloss>
You're responsible for loading the codegloss runtime in your HTML page so the custom element gets registered:
<script type="module" src="https://unpkg.com/codegloss/dist/index.js"></script>
This pattern works with marked, markdown-it, and any static site generator that goes through remark.
Edge cases
- Invalid JSON — if the annotations block contains malformed JSON, the plugin logs a warning and renders the code block without annotations
- Missing annotations block — the codegloss block renders as a CodeGloss instance with just the code, no annotations
- Duplicate imports — in
mdxmode, the plugin checks for existing CodeGloss imports before injecting one - Nested blocks — the plugin traverses child nodes recursively, so codegloss blocks inside blockquotes or other containers are detected
- Script tag escaping — in
htmlmode, the JSON payload escapes</scriptsequences to prevent breaking out of the script tag