or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-configuration.mdcomponent-interface.mdcomponent-overrides.mdcore-compilation.mdcustom-rendering.mdindex.mdutility-functions.md

custom-rendering.mddocs/

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

```