0
# Rendering System
1
2
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.
3
4
## Capabilities
5
6
### Renderer Class
7
8
The main renderer class that converts tokens to HTML.
9
10
```javascript { .api }
11
/**
12
* HTML renderer with customizable rule-based output generation
13
*/
14
class Renderer {
15
/** Render tokens to HTML */
16
render(tokens: Token[], options: RemarkableOptions, env: object): string;
17
/** Render inline tokens to HTML */
18
renderInline(tokens: Token[], options: RemarkableOptions, env: object): string;
19
/** Collection of rendering rules */
20
rules: RendererRules;
21
/** Helper function for generating line breaks */
22
getBreak: (tokens: Token[], idx: number) => string;
23
}
24
25
interface RendererRules {
26
[ruleName: string]: RendererRule;
27
}
28
29
type RendererRule = (
30
tokens: Token[],
31
idx: number,
32
options: RemarkableOptions,
33
env: object,
34
renderer: Renderer
35
) => string;
36
```
37
38
**Usage Examples:**
39
40
```javascript
41
import { Remarkable } from "remarkable";
42
43
const md = new Remarkable();
44
45
// Access renderer
46
const renderer = md.renderer;
47
48
// Render tokens directly
49
const tokens = md.parse('# Hello World');
50
const html = renderer.render(tokens, md.options, {});
51
```
52
53
### Default Rendering Rules
54
55
Built-in rendering rules for all standard markdown elements.
56
57
```javascript { .api }
58
interface DefaultRenderingRules {
59
// Block elements
60
blockquote_open: RendererRule;
61
blockquote_close: RendererRule;
62
code: RendererRule;
63
fence: RendererRule;
64
heading_open: RendererRule;
65
heading_close: RendererRule;
66
hr: RendererRule;
67
list_item_open: RendererRule;
68
list_item_close: RendererRule;
69
ordered_list_open: RendererRule;
70
ordered_list_close: RendererRule;
71
bullet_list_open: RendererRule;
72
bullet_list_close: RendererRule;
73
paragraph_open: RendererRule;
74
paragraph_close: RendererRule;
75
76
// Inline elements
77
text: RendererRule;
78
code_inline: RendererRule;
79
em_open: RendererRule;
80
em_close: RendererRule;
81
strong_open: RendererRule;
82
strong_close: RendererRule;
83
del_open: RendererRule;
84
del_close: RendererRule;
85
ins_open: RendererRule;
86
ins_close: RendererRule;
87
mark_open: RendererRule;
88
mark_close: RendererRule;
89
sub: RendererRule;
90
sup: RendererRule;
91
92
// Links and images
93
link_open: RendererRule;
94
link_close: RendererRule;
95
image: RendererRule;
96
97
// Tables
98
table_open: RendererRule;
99
table_close: RendererRule;
100
thead_open: RendererRule;
101
thead_close: RendererRule;
102
tbody_open: RendererRule;
103
tbody_close: RendererRule;
104
tr_open: RendererRule;
105
tr_close: RendererRule;
106
th_open: RendererRule;
107
th_close: RendererRule;
108
td_open: RendererRule;
109
td_close: RendererRule;
110
111
// HTML
112
htmlblock: RendererRule;
113
htmltag: RendererRule;
114
115
// Special
116
softbreak: RendererRule;
117
hardbreak: RendererRule;
118
}
119
```
120
121
### Custom Rendering Rules
122
123
Modifying or replacing rendering rules for custom output.
124
125
```javascript { .api }
126
/**
127
* Signature for custom rendering rules
128
*/
129
type CustomRendererRule = (
130
tokens: Token[],
131
idx: number,
132
options: RemarkableOptions,
133
env: object,
134
renderer: Renderer
135
) => string;
136
```
137
138
**Usage Examples:**
139
140
```javascript
141
const md = new Remarkable();
142
143
// Custom heading rule with anchors
144
md.renderer.rules.heading_open = function(tokens, idx, options, env, renderer) {
145
const token = tokens[idx];
146
const level = token.hLevel;
147
const next = tokens[idx + 1];
148
149
// Generate ID from heading text
150
let id = '';
151
if (next && next.type === 'inline') {
152
id = next.content.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
153
}
154
155
return `<h${level} id="${id}">`;
156
};
157
158
// Custom code block with line numbers
159
md.renderer.rules.fence = function(tokens, idx, options, env, renderer) {
160
const token = tokens[idx];
161
const lang = token.info ? ` class="language-${token.info}"` : '';
162
const lines = token.content.split('\n');
163
164
let result = '<pre><code' + lang + '>\n';
165
lines.forEach((line, i) => {
166
result += `<span class="line-number">${i + 1}</span>${escapeHtml(line)}\n`;
167
});
168
result += '</code></pre>\n';
169
170
return result;
171
};
172
173
// Custom image rule with lazy loading
174
md.renderer.rules.image = function(tokens, idx, options, env, renderer) {
175
const token = tokens[idx];
176
const src = token.attrGet('src');
177
const alt = token.content;
178
const title = token.attrGet('title');
179
180
let result = '<img loading="lazy"';
181
result += ` src="${escapeHtml(src)}"`;
182
result += ` alt="${escapeHtml(alt)}"`;
183
if (title) {
184
result += ` title="${escapeHtml(title)}"`;
185
}
186
result += ' />';
187
188
return result;
189
};
190
```
191
192
### Token Structure
193
194
Understanding token structure for custom rendering rules.
195
196
```javascript { .api }
197
interface Token {
198
/** Token type (e.g., 'paragraph_open', 'text', 'strong_open') */
199
type: string;
200
/** HTML tag name for the token */
201
tag?: string;
202
/** HTML attributes as [name, value] pairs */
203
attrs?: Array<[string, string]>;
204
/** Source line mapping [start, end] */
205
map?: [number, number];
206
/** Nesting level change: 1 (open), -1 (close), 0 (self-close) */
207
nesting?: number;
208
/** Current nesting level */
209
level?: number;
210
/** Child tokens for container tokens */
211
children?: Token[];
212
/** Text content for text tokens */
213
content?: string;
214
/** Markup characters that created this token */
215
markup?: string;
216
/** Additional info (e.g., language for code blocks) */
217
info?: string;
218
/** Arbitrary metadata */
219
meta?: any;
220
/** True for block-level tokens */
221
block?: boolean;
222
/** True if token should not be rendered */
223
hidden?: boolean;
224
225
// Helper methods for token attributes
226
attrGet(name: string): string | null;
227
attrSet(name: string, value: string): void;
228
attrPush(attr: [string, string]): void;
229
attrJoin(name: string, value: string): void;
230
}
231
```
232
233
**Usage Examples:**
234
235
```javascript
236
// Working with token attributes
237
function customLinkRule(tokens, idx, options, env, renderer) {
238
const token = tokens[idx];
239
240
// Get existing attributes
241
const href = token.attrGet('href');
242
const title = token.attrGet('title');
243
244
// Modify attributes
245
if (href && href.startsWith('http')) {
246
token.attrSet('target', '_blank');
247
token.attrSet('rel', 'noopener noreferrer');
248
}
249
250
// Render with modified attributes
251
let result = '<a';
252
if (token.attrs) {
253
token.attrs.forEach(([name, value]) => {
254
result += ` ${name}="${escapeHtml(value)}"`;
255
});
256
}
257
result += '>';
258
259
return result;
260
}
261
```
262
263
### Output Customization
264
265
Advanced techniques for customizing HTML output.
266
267
```javascript { .api }
268
/**
269
* Helper function for generating line breaks after block elements
270
*/
271
function getBreak(tokens: Token[], idx: number): string;
272
```
273
274
**Usage Examples:**
275
276
```javascript
277
// Custom wrapper for all paragraphs
278
md.renderer.rules.paragraph_open = function(tokens, idx, options, env, renderer) {
279
return '<div class="paragraph-wrapper"><p>';
280
};
281
282
md.renderer.rules.paragraph_close = function(tokens, idx, options, env, renderer) {
283
return '</p></div>' + renderer.getBreak(tokens, idx);
284
};
285
286
// Add custom CSS classes based on content
287
md.renderer.rules.blockquote_open = function(tokens, idx, options, env, renderer) {
288
const token = tokens[idx];
289
let className = 'blockquote';
290
291
// Check if next tokens contain specific patterns
292
const nextTokens = tokens.slice(idx + 1);
293
const hasWarning = nextTokens.some(t =>
294
t.type === 'text' && t.content.toLowerCase().includes('warning')
295
);
296
297
if (hasWarning) {
298
className += ' warning';
299
}
300
301
return `<blockquote class="${className}">`;
302
};
303
304
// Custom table rendering with Bootstrap classes
305
md.renderer.rules.table_open = function() {
306
return '<div class="table-responsive"><table class="table table-striped">\n';
307
};
308
309
md.renderer.rules.table_close = function() {
310
return '</table></div>\n';
311
};
312
```
313
314
### Template Systems Integration
315
316
Integrating with template systems and frameworks.
317
318
**Usage Examples:**
319
320
```javascript
321
// React component integration
322
md.renderer.rules.image = function(tokens, idx, options, env, renderer) {
323
const token = tokens[idx];
324
const src = token.attrGet('src');
325
const alt = token.content;
326
327
// Return React component syntax
328
return `<ImageComponent src="${src}" alt="${alt}" />`;
329
};
330
331
// Vue component integration
332
md.renderer.rules.fence = function(tokens, idx, options, env, renderer) {
333
const token = tokens[idx];
334
const lang = token.info;
335
const code = token.content;
336
337
if (lang === 'vue-demo') {
338
return `<VueDemo code="${escapeForAttribute(code)}" />`;
339
}
340
341
// Fallback to default code rendering
342
return defaultFenceRule(tokens, idx, options, env, renderer);
343
};
344
```