Rough Markdown
@rougher-stuff/markdown adds first-class hand-drawn annotations to CommonMark and GFM without taking over existing Markdown delimiters.
Directive syntax
Visual shorthand and the generic mark form normalize to the same AST node:
:underline[Important text]{color=#e5484d seed=42}
:mark[An API]{type=circle padding=4}
:::box{padding=8}
## An annotated section
:::Semantic directives let the runtime theme decide how an idea appears:
:important[Database migration]
:warning[Breaking API]
:question[Why does this happen?]
:todo[Implement the cache]
:changed[New behavior]Parse and render
import { parse, renderMarkdown } from '@rougher-stuff/markdown';
const tree = parse(markdown);
const html = renderMarkdown(markdown);parse() produces mdast with roughAnnotation, roughSemanticAnnotation, roughAnnotationContainer, roughReference, and roughCallout nodes. renderMarkdown() emits safe semantic HTML with data-rough-* enhancement hooks.
Mount annotations
import { mountRoughAnnotations, renderMarkdown } from '@rougher-stuff/markdown';
article.innerHTML = renderMarkdown(markdown);
const controller = mountRoughAnnotations(article, {
theme: {
warning: { type: 'box', color: '#e5484d', roughness: 1.8 },
},
});
controller.refresh();
await controller.hide().finished;
controller.remove();The DOM runtime delegates geometry, shared surfaces, drawing, and animation to @rougher-stuff/rough-notation.
Callouts
Link an inline target and a block note with a safe identifier:
:ref[reconciliation]{id=reconciliation}
:::callout{for=reconciliation color=#3b82f6}
React compares the old and new trees here.
:::The reference remains ordinary readable text without JavaScript. When mounted, the callout gains a marker and connector.
See the package README for the complete attribute schema and unified plugin API.
