Setup — Vanilla JS
Use NoirMD in any project without a framework. Pure DOM rendering with zero dependencies (except highlight.js, bundled).
Installation
npm install @noirmd/previewerOr via CDN (no build step needed):
<script type="module">
import { renderMarkdownString } from 'https://esm.sh/@noirmd/previewer/vanilla';
</script>⚠️ Required: Import vanilla.css
The vanilla renderer uses its own CSS file (vanilla.css, not markdown.css). Without importing it, content will render but have no visual styling — no heading sizes, no table borders, no code block backgrounds.
Import it once at the top of your entry point:
import '@noirmd/previewer/vanilla/vanilla.css';This applies to every usage of renderMarkdownString.
The vanilla CSS is self-contained — no Tailwind needed. It uses CSS custom properties (--nr-bg,--nr-text, etc.) for theming.
Basic Usage
import { renderMarkdownString } from '@noirmd/previewer/vanilla';
import '@noirmd/previewer/vanilla/vanilla.css';
const markdown = `# Hello World
This is **NoirMD** with vanilla JS.
:::note {title="Tip"}
You can use custom directives!
:::`;
// Returns a DOM element
const element = renderMarkdownString(markdown);
document.getElementById('app')!.appendChild(element);API
renderMarkdownString(markdown: string): HTMLElement
Parses markdown and returns a DOM element you can append anywhere.
import { renderMarkdownString } from '@noirmd/previewer/vanilla';
const el = renderMarkdownString('# Hello');
document.body.appendChild(el);parseMarkdown(markdown: string): Token[]
Returns the raw AST tokens if you want to build custom renderers:
import { parseMarkdown } from '@noirmd/previewer/core';
const tokens = parseMarkdown('# Hello **World**');
console.log(tokens);
// [{ type: 'header', level: 1, content: 'Hello **World**', ... }]Dynamic Updates
Re-render when content changes:
import { renderMarkdownString } from '@noirmd/previewer/vanilla';
import '@noirmd/previewer/vanilla/vanilla.css';
const container = document.getElementById('preview')!;
const textarea = document.getElementById('editor') as HTMLTextAreaElement;
textarea.addEventListener('input', () => {
container.innerHTML = '';
container.appendChild(renderMarkdownString(textarea.value));
});Styling
Vanilla renderer uses its own CSS (no Tailwind required):
import '@noirmd/previewer/vanilla/vanilla.css';Custom theming via CSS variables:
:root {
--nr-bg: #ffffff;
--nr-text: #1a1a2e;
--nr-accent: #6366f1;
--nr-border: #e2e8f0;
}
.dark {
--nr-bg: #0f0f23;
--nr-text: #e2e8f0;
--nr-accent: #818cf8;
--nr-border: #334155;
}HTML + Script Setup
Minimal HTML page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>NoirMD Vanilla</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; }
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import { renderMarkdownString } from '@noirmd/previewer/vanilla';
import '@noirmd/previewer/vanilla/vanilla.css';
const md = `# Hello **NoirMD**
:::note {title="It works!"}
Vanilla JS rendering with directives.
:::`;
document.getElementById('app').appendChild(renderMarkdownString(md));
</script>
</body>
</html>Framework Integration
Since vanilla renderer is pure DOM, it works anywhere:
- Svelte — use in
onMountor$effect - Angular — use in
ngAfterViewInit - Lit / Web Components — use in
connectedCallback - WordPress — enqueue as a script module
- Static sites — no build step required with esm.sh