0
# Custom Rendering
1
2
Complete control over individual rule rendering through custom render functions and rule system integration.
3
4
## Capabilities
5
6
### Custom Render Rule
7
8
Override the rendering of specific markdown elements with complete control over the output.
9
10
```typescript { .api }
11
/**
12
* Custom render rule function for complete rendering control
13
* @param next - Function to call for default rendering behavior
14
* @param node - Current AST node being rendered
15
* @param renderChildren - Function to render child nodes
16
* @param state - Current parser state with key for React elements
17
* @returns Custom React node or result of calling next()
18
*/
19
type RenderRuleFunction = (
20
next: () => React.ReactNode,
21
node: ParserResult,
22
renderChildren: RuleOutput,
23
state: State
24
) => React.ReactNode;
25
26
interface CustomRenderingOptions {
27
renderRule?: RenderRuleFunction;
28
}
29
30
type RuleOutput = (
31
ast: ParserResult | ParserResult[],
32
state: State
33
) => React.JSX.Element;
34
```
35
36
**Usage Examples:**
37
38
```typescript
39
import { compiler, RuleType } from "markdown-to-jsx";
40
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
41
42
// Custom rendering for code blocks with syntax highlighting
43
const customRenderRule = (next, node, renderChildren, state) => {
44
// Handle code blocks specially
45
if (node.type === RuleType.codeBlock) {
46
return (
47
<SyntaxHighlighter
48
key={state.key}
49
language={node.lang || 'text'}
50
className="syntax-highlighted"
51
>
52
{node.text}
53
</SyntaxHighlighter>
54
);
55
}
56
57
// Handle headings with custom anchors
58
if (node.type === RuleType.heading) {
59
const id = node.id;
60
const HeadingTag = `h${node.level}`;
61
62
return (
63
<HeadingTag key={state.key} id={id} className={`heading-${node.level}`}>
64
<a href={`#${id}`} className="heading-anchor" aria-hidden="true">
65
#
66
</a>
67
{renderChildren(node.children, state)}
68
</HeadingTag>
69
);
70
}
71
72
// Handle links with external link indicators
73
if (node.type === RuleType.link) {
74
const isExternal = node.target.startsWith('http');
75
76
return (
77
<a
78
key={state.key}
79
href={node.target}
80
title={node.title}
81
target={isExternal ? '_blank' : undefined}
82
rel={isExternal ? 'noopener noreferrer' : undefined}
83
className={isExternal ? 'external-link' : 'internal-link'}
84
>
85
{renderChildren(node.children, state)}
86
{isExternal && <span className="external-icon">๐</span>}
87
</a>
88
);
89
}
90
91
// Use default rendering for all other elements
92
return next();
93
};
94
95
const result = compiler(markdownContent, {
96
renderRule: customRenderRule
97
});
98
```
99
100
### Rule Type System
101
102
Comprehensive set of rule types for identifying and handling different markdown elements.
103
104
```typescript { .api }
105
/**
106
* Enumeration of all supported markdown rule types
107
* Values may change between versions - use constants, not hardcoded strings
108
*/
109
const RuleType: {
110
readonly blockQuote: "0";
111
readonly breakLine: "1";
112
readonly breakThematic: "2";
113
readonly codeBlock: "3";
114
readonly codeFenced: "4";
115
readonly codeInline: "5";
116
readonly footnote: "6";
117
readonly footnoteReference: "7";
118
readonly gfmTask: "8";
119
readonly heading: "9";
120
readonly headingSetext: "10";
121
readonly htmlBlock: "11";
122
readonly htmlComment: "12";
123
readonly htmlSelfClosing: "13";
124
readonly image: "14";
125
readonly link: "15";
126
readonly linkAngleBraceStyleDetector: "16";
127
readonly linkBareUrlDetector: "17";
128
readonly linkMailtoDetector: "18";
129
readonly newlineCoalescer: "19";
130
readonly orderedList: "20";
131
readonly paragraph: "21";
132
readonly ref: "22";
133
readonly refImage: "23";
134
readonly refLink: "24";
135
readonly table: "25";
136
readonly tableSeparator: "26";
137
readonly text: "27";
138
readonly textBolded: "28";
139
readonly textEmphasized: "29";
140
readonly textEscaped: "30";
141
readonly textMarked: "31";
142
readonly textStrikethroughed: "32";
143
readonly unorderedList: "33";
144
};
145
```
146
147
**Usage Examples:**
148
149
```typescript
150
import { RuleType } from "markdown-to-jsx";
151
152
const advancedRenderRule = (next, node, renderChildren, state) => {
153
switch (node.type) {
154
case RuleType.codeBlock:
155
case RuleType.codeFenced:
156
// Handle both types of code blocks
157
return <CustomCodeBlock key={state.key} {...node} />;
158
159
case RuleType.image:
160
// Custom image handling with lazy loading
161
return (
162
<img
163
key={state.key}
164
src={node.target}
165
alt={node.alt}
166
title={node.title}
167
loading="lazy"
168
className="responsive-image"
169
/>
170
);
171
172
case RuleType.table:
173
// Custom table with sorting
174
return <SortableTable key={state.key} data={node} />;
175
176
case RuleType.gfmTask:
177
// Custom task list item
178
return (
179
<CustomTaskItem
180
key={state.key}
181
completed={node.completed}
182
onChange={handleTaskToggle}
183
/>
184
);
185
186
case RuleType.blockQuote:
187
// Enhanced blockquotes with alert styling
188
const alertType = node.alert?.toLowerCase();
189
return (
190
<blockquote
191
key={state.key}
192
className={alertType ? `alert alert-${alertType}` : 'blockquote'}
193
>
194
{renderChildren(node.children, state)}
195
</blockquote>
196
);
197
198
default:
199
return next();
200
}
201
};
202
```
203
204
### Parser State Access
205
206
Access and utilize parser state information for context-aware rendering.
207
208
```typescript { .api }
209
/**
210
* Parser state object providing context about current parsing location
211
*/
212
interface State {
213
/** True if currently inside an anchor link */
214
inAnchor?: boolean;
215
/** True if parsing within HTML context */
216
inHTML?: boolean;
217
/** True if parsing in inline context */
218
inline?: boolean;
219
/** True if currently within a table */
220
inTable?: boolean;
221
/** React key for current element */
222
key?: React.Key;
223
/** True if currently within a list */
224
list?: boolean;
225
/** Previous capture string for context */
226
prevCapture?: string;
227
/** True if parsing in simple inline context */
228
simple?: boolean;
229
}
230
```
231
232
**Usage Examples:**
233
234
```typescript
235
const contextAwareRenderRule = (next, node, renderChildren, state) => {
236
// Different rendering based on context
237
if (node.type === RuleType.text) {
238
// Special handling for text in different contexts
239
if (state.inTable) {
240
return <span key={state.key} className="table-text">{node.text}</span>;
241
} else if (state.inAnchor) {
242
return <span key={state.key} className="link-text">{node.text}</span>;
243
} else if (state.list) {
244
return <span key={state.key} className="list-text">{node.text}</span>;
245
}
246
}
247
248
// Different link behavior based on context
249
if (node.type === RuleType.link && state.inTable) {
250
// Simplified links in tables
251
return (
252
<a key={state.key} href={node.target} className="table-link">
253
{renderChildren(node.children, state)}
254
</a>
255
);
256
}
257
258
return next();
259
};
260
```
261
262
### Advanced Rendering Patterns
263
264
Complex rendering scenarios combining multiple techniques for sophisticated output.
265
266
**Usage Examples:**
267
268
```typescript
269
import { compiler, RuleType } from "markdown-to-jsx";
270
271
// Rendering with plugins and extensions
272
const pluginRenderRule = (next, node, renderChildren, state) => {
273
// LaTeX math rendering
274
if (node.type === RuleType.codeBlock && node.lang === 'latex') {
275
return (
276
<div key={state.key} className="math-block">
277
<TeX math={node.text} block />
278
</div>
279
);
280
}
281
282
// Mermaid diagram rendering
283
if (node.type === RuleType.codeBlock && node.lang === 'mermaid') {
284
return (
285
<div key={state.key} className="mermaid-diagram">
286
<MermaidDiagram definition={node.text} />
287
</div>
288
);
289
}
290
291
// Interactive code examples
292
if (node.type === RuleType.codeBlock && node.lang === 'jsx-live') {
293
return (
294
<LiveCodeEditor
295
key={state.key}
296
code={node.text}
297
scope={{ React, useState, useEffect }}
298
/>
299
);
300
}
301
302
// Custom footnote handling
303
if (node.type === RuleType.footnoteReference) {
304
return (
305
<sup key={state.key}>
306
<a
307
href={node.target}
308
className="footnote-ref"
309
onClick={(e) => {
310
e.preventDefault();
311
scrollToFootnote(node.text);
312
}}
313
>
314
{node.text}
315
</a>
316
</sup>
317
);
318
}
319
320
return next();
321
};
322
323
// Conditional rendering based on configuration
324
const createConditionalRenderer = (config) => (next, node, renderChildren, state) => {
325
// Only render images if allowed
326
if (node.type === RuleType.image && !config.allowImages) {
327
return <span key={state.key}>[Image: {node.alt}]</span>;
328
}
329
330
// Filter out certain HTML elements
331
if (node.type === RuleType.htmlBlock && config.blockedTags.includes(node.tag)) {
332
return <span key={state.key}>[Blocked HTML element]</span>;
333
}
334
335
// Add analytics to links
336
if (node.type === RuleType.link) {
337
return (
338
<a
339
key={state.key}
340
href={node.target}
341
onClick={() => config.trackLink(node.target)}
342
>
343
{renderChildren(node.children, state)}
344
</a>
345
);
346
}
347
348
return next();
349
};
350
351
// Usage with custom configuration
352
const secureOptions = {
353
renderRule: createConditionalRenderer({
354
allowImages: false,
355
blockedTags: ['script', 'iframe', 'object'],
356
trackLink: (url) => analytics.track('link_click', { url })
357
})
358
};
359
```
360
361
### Error Handling in Custom Rendering
362
363
Robust error handling for custom render functions to prevent rendering failures.
364
365
```typescript
366
const safeRenderRule = (next, node, renderChildren, state) => {
367
try {
368
// Custom rendering logic that might throw
369
if (node.type === RuleType.codeBlock) {
370
return <ComplexCodeRenderer key={state.key} code={node.text} />;
371
}
372
373
return next();
374
} catch (error) {
375
// Fallback to default rendering on error
376
console.warn('Custom rendering failed:', error);
377
return next();
378
}
379
};
380
```