Codefence Renderers
Mix.install([
{:mdex, ">= 0.11.6"},
{:pikchr, "~> 0.5"},
{:svg_charts, "~> 0.5.0"},
{:kino, "~> 0.16"}
])
Intro
Codefence renderers let you plug in custom renderers for fenced code blocks based on the language identifier. Any function that takes (lang, meta, code) and returns an HTML string can be used.
This is useful for rendering diagrams, math, or other specialized content directly in Markdown.
Basic Usage
The simplest example wraps code in a custom HTML element:
markdown = """
# Custom Block
```alert
This is important!
```
"""
html =
MDEx.to_html!(markdown,
render: [unsafe: true],
syntax_highlight: nil,
codefence_renderers: %{
"alert" => fn _lang, _meta, code ->
~s(#{String.trim(code)})
end
}
)
Kino.HTML.new(html)
Pikchr Diagrams
Pikchr is a PIC-like markup language for diagrams. The pikchr package renders it to SVG via a precompiled NIF — no system dependencies needed.
markdown = """
# System Architecture
```pikchr
right
Client: box "Client" fit
arrow 300% "request" above
Server: box "Server" fit
arrow 300% "query" above
DB: box "DB" fit
arrow from DB.s down 50% then left until even with Server then to Server.s "rows" below
arrow from Server.s down 100% then left until even with Client then to Client.s "response" below
```
Regular Elixir code still gets syntax highlighted:
```elixir
MDEx.to_html!("# Hello")
```
"""
html =
MDEx.to_html!(markdown,
render: [unsafe: true],
codefence_renderers: %{
"pikchr" => fn _lang, _meta, code -> Pikchr.render!(code) end
}
)
Kino.HTML.new(html)
Multiple Renderers
You can register multiple renderers at once. Unregistered languages fall through to the default syntax highlighter:
markdown = """
```pikchr
box "Hello" fit
arrow
box "World" fit
```
```csv
Name,Age
Alice,30
Bob,25
```
```elixir
Enum.map(1..5, & &1 * 2)
```
"""
html =
MDEx.to_html!(markdown,
render: [unsafe: true],
codefence_renderers: %{
"pikchr" => fn _lang, _meta, code -> Pikchr.render!(code) end,
"csv" => fn _lang, _meta, code ->
rows =
code
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.split(&1, ","))
header = "" <> Enum.map_join(hd(rows), "", &"#{&1} ") <> " "
body = Enum.map_join(tl(rows), "", fn row ->
"" <> Enum.map_join(row, "", &"#{&1} ") <> " "
end)
"#{header}#{body}
"
end
}
)
Kino.HTML.new(html)
SVG Charts
svg_charts renders charts to SVG via a precompiled NIF wrapping charts-rs. The chart type and data are specified as JSON directly in the code fence:
markdown = """
# Sales Report
```chart
{"type": "bar", "width": 630, "height": 410, "title_text": "Weekly Revenue", "title_height": 30, "legend_margin": {"top": 35}, "series_list": [{"name": "Online", "data": [120.0, 132.0, 101.0, 134.0, 90.0, 230.0, 210.0]}, {"name": "In-Store", "data": [220.0, 182.0, 191.0, 234.0, 290.0, 330.0, 310.0]}], "x_axis_data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]}
```
```chart
{"type": "pie", "width": 500, "height": 400, "title_text": "Traffic Sources", "title_height": 30, "legend_margin": {"top": 35}, "series_list": [{"name": "Search", "data": [1048.0]}, {"name": "Direct", "data": [735.0]}, {"name": "Email", "data": [580.0]}, {"name": "Social", "data": [484.0]}]}
```
"""
html =
MDEx.to_html!(markdown,
render: [unsafe: true],
syntax_highlight: nil,
codefence_renderers: %{
"chart" => fn _lang, _meta, code -> SvgCharts.render!(code) end
}
)
Kino.HTML.new(html)
Using the Meta String
The info string after the language name is passed as the meta argument. You can use it for configuration:
markdown = """
```pikchr dark
box "Dark" fit
arrow
box "Mode" fit
```
"""
html =
MDEx.to_html!(markdown,
render: [unsafe: true],
codefence_renderers: %{
"pikchr" => fn _lang, meta, code ->
opts = if String.contains?(meta, "dark"), do: [dark_mode: true], else: []
svg = Pikchr.render!(code, opts)
if String.contains?(meta, "dark") do
~s(#{svg})
else
svg
end
end
}
)
Kino.HTML.new(html)