High-performance Markdown parser with full CommonMark support, syntax extensions, and configurable rule systems.
—
The Remarkable rendering system converts parsed tokens into HTML output through a customizable rule-based renderer. Each token type has a corresponding rendering rule that can be modified or replaced.
The main renderer class that converts tokens to HTML.
/**
* HTML renderer with customizable rule-based output generation
*/
class Renderer {
/** Render tokens to HTML */
render(tokens: Token[], options: RemarkableOptions, env: object): string;
/** Render inline tokens to HTML */
renderInline(tokens: Token[], options: RemarkableOptions, env: object): string;
/** Collection of rendering rules */
rules: RendererRules;
/** Helper function for generating line breaks */
getBreak: (tokens: Token[], idx: number) => string;
}
interface RendererRules {
[ruleName: string]: RendererRule;
}
type RendererRule = (
tokens: Token[],
idx: number,
options: RemarkableOptions,
env: object,
renderer: Renderer
) => string;Usage Examples:
import { Remarkable } from "remarkable";
const md = new Remarkable();
// Access renderer
const renderer = md.renderer;
// Render tokens directly
const tokens = md.parse('# Hello World');
const html = renderer.render(tokens, md.options, {});Built-in rendering rules for all standard markdown elements.
interface DefaultRenderingRules {
// Block elements
blockquote_open: RendererRule;
blockquote_close: RendererRule;
code: RendererRule;
fence: RendererRule;
heading_open: RendererRule;
heading_close: RendererRule;
hr: RendererRule;
list_item_open: RendererRule;
list_item_close: RendererRule;
ordered_list_open: RendererRule;
ordered_list_close: RendererRule;
bullet_list_open: RendererRule;
bullet_list_close: RendererRule;
paragraph_open: RendererRule;
paragraph_close: RendererRule;
// Inline elements
text: RendererRule;
code_inline: RendererRule;
em_open: RendererRule;
em_close: RendererRule;
strong_open: RendererRule;
strong_close: RendererRule;
del_open: RendererRule;
del_close: RendererRule;
ins_open: RendererRule;
ins_close: RendererRule;
mark_open: RendererRule;
mark_close: RendererRule;
sub: RendererRule;
sup: RendererRule;
// Links and images
link_open: RendererRule;
link_close: RendererRule;
image: RendererRule;
// Tables
table_open: RendererRule;
table_close: RendererRule;
thead_open: RendererRule;
thead_close: RendererRule;
tbody_open: RendererRule;
tbody_close: RendererRule;
tr_open: RendererRule;
tr_close: RendererRule;
th_open: RendererRule;
th_close: RendererRule;
td_open: RendererRule;
td_close: RendererRule;
// HTML
htmlblock: RendererRule;
htmltag: RendererRule;
// Special
softbreak: RendererRule;
hardbreak: RendererRule;
}Modifying or replacing rendering rules for custom output.
/**
* Signature for custom rendering rules
*/
type CustomRendererRule = (
tokens: Token[],
idx: number,
options: RemarkableOptions,
env: object,
renderer: Renderer
) => string;Usage Examples:
const md = new Remarkable();
// Custom heading rule with anchors
md.renderer.rules.heading_open = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
const level = token.hLevel;
const next = tokens[idx + 1];
// Generate ID from heading text
let id = '';
if (next && next.type === 'inline') {
id = next.content.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
}
return `<h${level} id="${id}">`;
};
// Custom code block with line numbers
md.renderer.rules.fence = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
const lang = token.info ? ` class="language-${token.info}"` : '';
const lines = token.content.split('\n');
let result = '<pre><code' + lang + '>\n';
lines.forEach((line, i) => {
result += `<span class="line-number">${i + 1}</span>${escapeHtml(line)}\n`;
});
result += '</code></pre>\n';
return result;
};
// Custom image rule with lazy loading
md.renderer.rules.image = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
const src = token.attrGet('src');
const alt = token.content;
const title = token.attrGet('title');
let result = '<img loading="lazy"';
result += ` src="${escapeHtml(src)}"`;
result += ` alt="${escapeHtml(alt)}"`;
if (title) {
result += ` title="${escapeHtml(title)}"`;
}
result += ' />';
return result;
};Understanding token structure for custom rendering rules.
interface Token {
/** Token type (e.g., 'paragraph_open', 'text', 'strong_open') */
type: string;
/** HTML tag name for the token */
tag?: string;
/** HTML attributes as [name, value] pairs */
attrs?: Array<[string, string]>;
/** Source line mapping [start, end] */
map?: [number, number];
/** Nesting level change: 1 (open), -1 (close), 0 (self-close) */
nesting?: number;
/** Current nesting level */
level?: number;
/** Child tokens for container tokens */
children?: Token[];
/** Text content for text tokens */
content?: string;
/** Markup characters that created this token */
markup?: string;
/** Additional info (e.g., language for code blocks) */
info?: string;
/** Arbitrary metadata */
meta?: any;
/** True for block-level tokens */
block?: boolean;
/** True if token should not be rendered */
hidden?: boolean;
// Helper methods for token attributes
attrGet(name: string): string | null;
attrSet(name: string, value: string): void;
attrPush(attr: [string, string]): void;
attrJoin(name: string, value: string): void;
}Usage Examples:
// Working with token attributes
function customLinkRule(tokens, idx, options, env, renderer) {
const token = tokens[idx];
// Get existing attributes
const href = token.attrGet('href');
const title = token.attrGet('title');
// Modify attributes
if (href && href.startsWith('http')) {
token.attrSet('target', '_blank');
token.attrSet('rel', 'noopener noreferrer');
}
// Render with modified attributes
let result = '<a';
if (token.attrs) {
token.attrs.forEach(([name, value]) => {
result += ` ${name}="${escapeHtml(value)}"`;
});
}
result += '>';
return result;
}Advanced techniques for customizing HTML output.
/**
* Helper function for generating line breaks after block elements
*/
function getBreak(tokens: Token[], idx: number): string;Usage Examples:
// Custom wrapper for all paragraphs
md.renderer.rules.paragraph_open = function(tokens, idx, options, env, renderer) {
return '<div class="paragraph-wrapper"><p>';
};
md.renderer.rules.paragraph_close = function(tokens, idx, options, env, renderer) {
return '</p></div>' + renderer.getBreak(tokens, idx);
};
// Add custom CSS classes based on content
md.renderer.rules.blockquote_open = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
let className = 'blockquote';
// Check if next tokens contain specific patterns
const nextTokens = tokens.slice(idx + 1);
const hasWarning = nextTokens.some(t =>
t.type === 'text' && t.content.toLowerCase().includes('warning')
);
if (hasWarning) {
className += ' warning';
}
return `<blockquote class="${className}">`;
};
// Custom table rendering with Bootstrap classes
md.renderer.rules.table_open = function() {
return '<div class="table-responsive"><table class="table table-striped">\n';
};
md.renderer.rules.table_close = function() {
return '</table></div>\n';
};Integrating with template systems and frameworks.
Usage Examples:
// React component integration
md.renderer.rules.image = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
const src = token.attrGet('src');
const alt = token.content;
// Return React component syntax
return `<ImageComponent src="${src}" alt="${alt}" />`;
};
// Vue component integration
md.renderer.rules.fence = function(tokens, idx, options, env, renderer) {
const token = tokens[idx];
const lang = token.info;
const code = token.content;
if (lang === 'vue-demo') {
return `<VueDemo code="${escapeForAttribute(code)}" />`;
}
// Fallback to default code rendering
return defaultFenceRule(tokens, idx, options, env, renderer);
};Install with Tessl CLI
npx tessl i tessl/npm-remarkable