netsi-marked
v0.1.0
Published
A vanilla JS custom element that renders Markdown with marked, plugins, Mermaid diagrams, a11y, i18n, CSS variables and copy actions.
Downloads
56
Maintainers
Readme
netsi-marked
netsi-marked is a vanilla JavaScript custom element for rendering Markdown with marked, safe HTML sanitizing, Mermaid diagrams, code block enhancements, CSS variables, i18n, custom events, container-query friendly layouts, and copy actions.
Install
npm install netsi-markedimport 'netsi-marked';JSR target:
npx jsr add @netsi/markedimport '@netsi/marked';Basic usage
<netsi-marked locale="en" plugins="mermaid,code-enhance,heading-anchors">
<template type="text/markdown">
# Hello Markdown
```js
console.log('Rendered by netsi-marked');Loading a .md file
<netsi-marked src="./docs/page.md" plugins="mermaid,code-enhance"></netsi-marked>JavaScript API
const element = document.querySelector('netsi-marked');
element.markdown = '# Updated markdown';
await element.copyMarkdown();
await element.copyFormatted();Attributes
| Attribute | Example | Description |
|---|---|---|
| src | src="./demo.md" | Fetches markdown from a URL. |
| plugins | plugins="mermaid,code-enhance" | Comma-separated plugin list. |
| locale | locale="da" | UI language. Defaults to document language or English. |
| theme | theme="dark" | Hint for Mermaid/theme-aware plugins. |
| sanitize | sanitize="false" | Sanitizing is enabled by default. Disable only for trusted content. |
| plain | plain | Hides the toolbar. |
| breaks | breaks | Enables GFM line breaks in marked. |
Built-in plugins
callouts— supports GitHub-style callouts such as> [!NOTE].mermaid— lazy-loads Mermaid only when a Mermaid code fence is present.code-enhance— adds code copy buttons and optional Highlight.js formatting.heading-anchors— adds heading ids and anchor links.external-links— addstarget="_blank"and saferelvalues to external links.
Custom plugins
import { NetsiMarked } from 'netsi-marked';
NetsiMarked.use({
name: 'word-count',
afterRender(root, context) {
const count = root.innerText.trim().split(/\s+/).length;
context.dispatch('plugin-load', { plugin: 'word-count', count });
}
});A plugin can expose:
type NetsiMarkedPlugin = {
name: string;
test?: (markdown: string) => boolean;
beforeParse?: (markdown: string, context: object) => string | Promise<string>;
afterRender?: (root: HTMLElement, context: object) => void | Promise<void>;
};Events
All events bubble and are composed.
| Event | Description |
|---|---|
| netsi-marked:render-start | Fired before markdown is parsed. |
| netsi-marked:render-complete | Fired after plugins finish. |
| netsi-marked:render-error | Fired when rendering fails. |
| netsi-marked:copy | Fired after markdown, formatted content, or code is copied. |
| netsi-marked:copy-error | Fired if formatted copying fails. |
| netsi-marked:plugin-load | Fired when a plugin has been applied or lazy-loaded. |
| netsi-marked:theme-change | Used by the complete demo when the theme toggle changes. |
document.addEventListener('netsi-marked:render-complete', (event) => {
console.log(event.detail);
});CSS variables
netsi-marked {
--netsi-marked-font: system-ui, sans-serif;
--netsi-marked-mono: ui-monospace, monospace;
--netsi-marked-bg: Canvas;
--netsi-marked-fg: CanvasText;
--netsi-marked-accent: #0969da;
--netsi-marked-border: color-mix(in srgb, CanvasText 18%, transparent);
--netsi-marked-radius: 0.75rem;
--netsi-marked-pad: 1rem;
}The rendered markdown is projected into Light DOM as .netsi-marked-content, so page-level CSS can style headings, code blocks, diagrams, callouts, and tables.
Accessibility and i18n
- Toolbar actions are real buttons with keyboard support.
- Copy/render feedback is announced through an
aria-livestatus region. - The demo includes focus states and
prefers-reduced-motionhandling. - UI labels are locale-driven.
import { NetsiMarked } from 'netsi-marked';
NetsiMarked.setLocale('eo', {
copyMarkdown: 'Kopii MD',
copyFormatted: 'Kopii formatitan',
copied: 'Kopiita'
});About is="netsi-marked"
There are two custom element families:
- Autonomous custom elements, such as
<netsi-marked>. - Customized built-in elements, such as
<textarea is="netsi-marked-textarea">.
The same custom element name cannot be registered both ways in the same registry. Because this package prioritizes the autonomous <netsi-marked> element, it does not register is="netsi-marked".
The optional built-in helper uses a distinct name:
import { defineNetsiMarkedBuiltIns } from 'netsi-marked/built-ins';
defineNetsiMarkedBuiltIns();<textarea is="netsi-marked-textarea" preview-target="#preview"></textarea>
<netsi-marked id="preview"></netsi-marked>Customized built-ins are not supported by Safari, so they are not used as the primary API.
Test demos
Run a local static server from the project root:
npm run demoThen open:
test/complete-demo/test/npm-simple-example/test/jsr-simple-example/
The markdown fixtures are in test/markdown/ and use My Big TOE-inspired content with Mermaid diagrams and code examples.
The npm-simple-example and jsr-simple-example load published packages through browser ESM URLs:
- npm:
https://esm.sh/netsi-marked - JSR:
https://esm.sh/jsr/@netsi/marked
CodePen Pen 2.0 notes
The complete demo is already split into multiple files. For CodePen Pen 2.0, copy these files:
test/complete-demo/index.htmltest/complete-demo/main.jsstyles/netsi-marked.csssrc/index.jssrc/netsi-marked.jssrc/plugins.jssrc/locales.jssrc/built-ins.jstest/markdown/my-big-toe-demo.md
For a single remote import CodePen variant, replace local source imports with an ESM CDN import after publishing.
Publish checklist
- Update
package.jsonandjsr.jsonversions. - Run
npm install. - Run
npm test. - Run
npm pack --dry-run. - Run
npx jsr publish --dry-run. - Push to GitHub and create a GitHub Release to trigger
.github/workflows/publish.yml.
