@soufianeh/annotate-code
v0.1.1
Published
Link code lines to explanations — an Astro integration and remark plugin with hover highlighting.
Maintainers
Readme
annotate-code
A lightweight widget for linking code lines to explanations — inspired by diveintopython3.net.
Mark specific lines (or multiline chunks) with [N] inline comments. Hovering an explanation highlights the matching code, and vice versa.

Usage with Astro
Integration (recommended)
The Astro integration registers the remark plugin and injects the stylesheet automatically — nothing else to configure.
npx astro add @soufianeh/annotate-codeOr manually:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import annotateCode from '@soufianeh/annotate-code';
export default defineConfig({
integrations: [annotateCode({ theme: 'github-light' })],
});Then write annotated code blocks in any .md or .mdx page — no imports needed:
```python annotate
def fibonacci(n):
if n <= 1: # [1]
return n
a, b = 0, 1
for _ in range(n - 1): # [2]
a, b = b, a + b # [3]
return b # [4]
---
1: Base cases: fibonacci(0) = 0 and fibonacci(1) = 1.
2: We already know fib(1) = 1, so we only need n - 1 more steps.
3: Simultaneous assignment — both sides evaluate before either variable updates.
4: b always holds the latest value in the sequence.
```Integration options
annotateCode({
theme: 'github-dark', // any Shiki theme — default: 'github-light'
})Remark plugin (manual)
If you need more control (e.g. you're using the remark plugin outside Astro, or you want to manage the stylesheet yourself):
// astro.config.mjs
import { defineConfig } from 'astro/config';
import remarkAnnotateCode from '@soufianeh/annotate-code/remark';
export default defineConfig({
markdown: {
remarkPlugins: [
[remarkAnnotateCode, { theme: 'github-light' }],
],
},
});Add the stylesheet to your layout's <head>:
// src/layouts/Layout.astro
import '@soufianeh/annotate-code/styles';Plugin options
remarkAnnotateCode({
theme: 'github-dark', // any Shiki theme — default: 'github-light'
stylesheetHref: '/ac.css', // inject a <link> tag per widget — optional
})How it works
The remark plugin runs at build time and replaces each annotated fenced code block with static HTML:
- AST traversal — finds every
codenode whose meta string includes"annotate". - Block parsing — splits the block on
---: code (with[N]markers) above,N: textannotations below. - Marker stripping — removes
[N]markers from each line and records alineIndex → annotationIdmap. - Shiki highlighting — highlights the clean code server-side.
- HTML generation — wraps each line in a
.ac-linespan withdata-ann-id, adds callout badges, builds an.ac-listof annotation items. - Node replacement — swaps the
codeAST node for ahtmlnode with the widget HTML and a one-time inline hover script.
No client-side framework or runtime highlighter required.
Vanilla JS usage
For plain HTML pages without a build pipeline.
1. Call annotate()
import { annotate } from '@soufianeh/annotate-code/browser';
annotate(document.getElementById('demo'), {
code: `def fibonacci(n):
if n <= 1: # [1]
return n
a, b = 0, 1
for _ in range(n - 1): # [2]
a, b = b, a + b # [3]
return b # [4]`,
annotations: [
{ id: 1, text: 'Base cases: fibonacci(0) = 0 and fibonacci(1) = 1.' },
{ id: 2, text: 'We already know fib(1) = 1, so we only need n - 1 more steps.' },
{ id: 3, text: 'Simultaneous assignment — both sides evaluate before either variable updates.' },
{ id: 4, text: 'b always holds the latest value in the sequence.' },
],
highlight: (code) => // optional — bring your own highlighter
Prism.highlight(code, Prism.languages.python, 'python'),
});2. Include the stylesheet
<link rel="stylesheet" href="node_modules/@soufianeh/annotate-code/demo/styles.css" />Or import via a bundler:
import '@soufianeh/annotate-code/styles';Annotation syntax
Single-line:
import os # [1]Multiline — use the same number on consecutive lines; the callout badge appears on the last one:
def fibonacci(n): # [3]
if n <= 1: # [3]
return n # [3]Supported comment styles: // [N], # [N], -- [N], or bare [N].
API
// Astro integration ('@soufianeh/annotate-code')
type AnnotateCodeOptions = { theme?: string }; // Shiki theme — default: 'github-light'
export default function annotateCode(options?: AnnotateCodeOptions): AstroIntegration;
// Remark plugin ('@soufianeh/annotate-code/remark')
type RemarkAnnotateCodeOptions = {
theme?: string; // Shiki theme — default: 'github-light'
stylesheetHref?: string; // inject a <link> tag per widget — optional
};
export default function remarkAnnotateCode(options?: RemarkAnnotateCodeOptions): Plugin;
// Browser ('@soufianeh/annotate-code/browser')
type AnnotateOptions = {
code: string;
annotations: Array<{ id: number; text: string }>;
highlight?: (code: string) => string; // receives clean code (markers stripped)
};
function annotate(container: HTMLElement, options: AnnotateOptions): void;Dev
pnpm install
pnpm dev # build + browser-sync with live reload
pnpm build # compile TypeScript once
pnpm demo:remark # regenerate demo/remark.html (remark plugin output)Open http://localhost:3000:
demo/index.html— vanilla JS demo (Prism highlighting, runtime)demo/remark.html— remark plugin demo (Shiki highlighting, build-time)
