AvalonMarkdown 10.0.0

dotnet add package AvalonMarkdown --version 10.0.0
                    
NuGet\Install-Package AvalonMarkdown -Version 10.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AvalonMarkdown" Version="10.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AvalonMarkdown" Version="10.0.0" />
                    
Directory.Packages.props
<PackageReference Include="AvalonMarkdown" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AvalonMarkdown --version 10.0.0
                    
#r "nuget: AvalonMarkdown, 10.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AvalonMarkdown@10.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AvalonMarkdown&version=10.0.0
                    
Install as a Cake Addin
#tool nuget:?package=AvalonMarkdown&version=10.0.0
                    
Install as a Cake Tool

AvalonMarkdown

A Markdown preview control for AvaloniaUI β€” renders Markdown with math (KaTeX), syntax highlighting (highlight.js), diagrams (Mermaid / PlantUML), function plots, and video embeds.

🌏 δΈ­ζ–‡ζ–‡ζ‘£

Installation

dotnet add package AvalonMarkdown

Everything you need lives on two types:

  • MarkdownView β€” show a Markdown document and react to the user (links, scroll, headings).
  • MarkdownThemeViewModel β€” style how that document looks.

MarkdownView β€” rendering & interaction

Add the control to a window and feed it Markdown content.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:md="clr-namespace:AvalonMarkdown.Views;assembly=AvalonMarkdown">
    <md:MarkdownView x:Name="Preview" />
</Window>

Show content

Bind the Text property (this is the recommended way β€” it updates automatically):

<md:MarkdownView Text="{Binding MarkdownContent}" />

Or render explicitly after the control is ready:

Preview.OnReady += async (_, _) =>
{
    await Preview.RenderMarkdownAsync("# Hello World\n\n**Bold** *Italic*");
};

When content is empty you can show a hint with Placeholder (blank by default):

<md:MarkdownView Text="{Binding MarkdownContent}" Placeholder="Type something…" />

Function plots

A plot fence draws y = f(x). Its body is the function-plot options object as raw JSON β€” pass through anything the library documents:

```plot
{
  "title": "sin(x)/x",
  "grid": true,
  "xAxis": { "domain": [-12, 12] },
  "yAxis": { "domain": [-0.4, 1.1] },
  "data": [
    { "fn": "sin(x)/x" },
    { "fn": "1/x", "color": "#e06c75" }
  ]
}
```

Only two things are ever defaulted, and your JSON always wins:

  • color β€” a series without one takes a colour from the theme palette, by index. Series colours are per-function (color is a per-datum option), so they cannot come from CSS.
  • disableZoom β€” defaults to true, keeping the graph static so the wheel keeps scrolling the page. Set "disableZoom": false to enable wheel zoom and panning on that block. This default is deliberate: the library's zoom binding consumes every wheel event over the graph, which traps the scroll wheel in a reading pane.

width and height are the whole SVG including margins (left 40 / right 20 / bottom 20, top 20 β€” or 40 when there is a title), so "height": 350 yields a 310px plot area. If you omit width, it is fitted to the container.

A malformed body renders an inline error block instead of breaking the preview. The fence is matched on its info string exactly, so ordinary code fences β€” json included β€” are untouched.

Expression strings are checked against a character whitelist before they reach the library's evaluator (which compiles them to JavaScript). Comparison, ternary and modulo operators are allowed, so piecewise definitions work β€” "fn": "x > 0 ? x : -x" β€” but quotes, semicolons, braces, brackets and backslashes are rejected.

Two things to know about the expression syntax, both straight from the library:

  • Constants are upper-case β€” PI and E, not pi / e. Lower-case forms are undefined and the series silently fails to draw.
  • Available functions include sin cos tan asin acos atan sqrt abs exp log ln max min. Names outside that set (floor, sign, …) are undefined.

Every hyperlink click fires LinkClicked. Set Handled = true to take over navigation yourself; otherwise the link opens in the system browser.

Preview.LinkClicked += (_, e) =>
{
    if (e.Url.StartsWith("app://"))
    {
        OpenInternalPage(e.Url);   // your in-app routing
        e.Handled = true;          // suppress the default browser launch
    }
};

Jump inside the document (anchors & TOC)

Markdown headings are linkable from within the same page, so [See Β§2](#2-text-formatting)-style links just work.

To build a clickable table of contents in your own UI:

var headings = await Preview.GetHeadingsAsync();          // [{ Text, Level }, …]
var ok       = await Preview.NavigateToHeadingAsync("Chapter 2"); // scroll to a heading

Sync an editor with the preview (scroll progress)

Report/restore the vertical read position β€” handy for editor ⇄ preview split views:

double? progress = await Preview.GetScrollProgressAsync(); // 0–100
await Preview.ScrollToProgressAsync(50);

Export a diagram as an image

Right-click a Mermaid diagram, a function plot or a PlantUML image β€” or long-press it on touch β€” and choose Save as SVG or Save as PNG. The file is written through the platform's save dialog.

Only those three members are intercepted. Right-clicking anywhere else in the document still shows the platform's own menu. Set EnableContextMenu="False" to turn the feature off entirely and get the platform menu back everywhere:

<md:MarkdownView Text="{Binding MarkdownContent}" EnableContextMenu="False" />

Math is deliberately not exportable: KaTeX renders positioned HTML, not SVG, so there is no vector artefact to hand out β€” producing an image would mean screenshotting the DOM.

Three things worth knowing:

  • An exported PlantUML image is black-on-white, even in dark theme. That is PlantUML's own output, deliberately unfiltered β€” the dark-theme inversion you see on screen is a display-only filter. It prints and pastes correctly, but it will look different from a dark-theme Mermaid export.
  • Exported Mermaid and plot images carry an opaque background taken from the page, so a dark-theme diagram stays legible when pasted onto a white page.
  • A very large diagram may refuse to save as PNG (reported in the preview) β€” the JSβ†’C# bridge crosses Android's Binder with a ~1 MB limit. SVG has no such limit; use it, or export a smaller diagram.

Configuration & diagnostics

Content updates auto-scroll to the newest content, and pause while you operate the scrollbar. Behaviour can be tuned with ApplyConfigAsync:

await Preview.ApplyConfigAsync("setPreviewConfig({ autoScrollOnUpdate: true, autoScrollCooldown: 2000 })");

ErrorOccurred and ConsoleMessage events let you surface or log any rendering/script message.

MarkdownView reference

Member Type What it does
Text string? Markdown content (two-way bindable)
Placeholder string? Hint shown when content is empty
EnableContextMenu bool Right-click / long-press an image to save it (default true)
OnReady event Fires when the preview is ready to render
LinkClicked event Fires on hyperlink clicks; Handled = true takes over navigation
ErrorOccurred event Fires on recoverable internal errors
ConsoleMessage event Fires for each WebView console message (log/warn/error)
RenderMarkdownAsync(text) Task Renders Markdown content
GetHeadingsAsync() Task<IReadOnlyList<MarkdownHeading>> Lists all headings (Text, Level)
NavigateToHeadingAsync(heading) Task<bool> Scrolls to the heading; returns whether it was found
GetScrollProgressAsync() Task<double?> Current vertical progress (0–100)
ScrollToProgressAsync(percent) Task Scrolls to a vertical progress position (0–100)
ApplyConfigAsync(expression) Task Applies preview behaviour configuration
ApplyCustomCssAsync(css) Task Applies a full custom stylesheet
RestartPreviewAsync() Task Recreates the preview page

MarkdownThemeViewModel β€” styling

Configures how the document looks: colors, typography, code highlighting, and diagram styling. Every property carries a [Description] (hover it in IntelliSense to see what it controls).

Option A β€” use the built-in theme editor

MarkdownThemeView is a ready-made editor panel for the properties below. Point it at your preview; any change is applied immediately.

<md:MarkdownView x:Name="Preview" />
<md:MarkdownThemeView Target="{Binding #Preview}" />

The panel is collapsed by default β€” click its header to expand it.

Option B β€” drive it programmatically

Create a MarkdownThemeViewModel, change the properties you care about, and let it push changes to one or more previews:

var theme = new MarkdownThemeViewModel
{
    BgR = 30, BgG = 30, BgB = 30,   // page background
    TextR = 212, TextG = 212, TextB = 212, // body text
    HljsKeyword = "#569cd6",
    MermaidContainerPadding = 8,
};

theme.RegisterRenderer(myPreview);
theme.StartAutoApply();   // any further property change auto-applies

Prefer a one-shot apply instead?

await myPreview.ApplyCustomCssAsync(theme.GenerateCss());

The same styling applies whatever light/dark theme your app currently uses.

What you can configure

Group Covers
Core colors Page background, text, links, headings, inline code, borders
Surface colors Secondary background (e.g. code headers), secondary text, inline-code background, code-block background, table header
Typography Code font size; code-block / pre corner radius
Code highlighting Color of each syntax token (keyword, string, number, comment, …)
Diagrams Mermaid & PlantUML container background / padding / margin / radius; PlantUML dark invert strength
Function plots Plot container background / padding / margin / radius; axis, grid and title colour

Supported style properties

Colors β€” each surface is three int channels R / G / B (0–255); each XxxHex is a read-only #RRGGBB preview.

Surface Channel properties Preview
Background BgR BgG BgB BgHex
Body text TextR TextG TextB TextHex
Link LinkR LinkG LinkB LinkHex
Heading HeadingR HeadingG HeadingB HeadingHex
Inline code text CodeR CodeG CodeB CodeHex
Border BorderR BorderG BorderB BorderHex
Secondary background BgSecR BgSecG BgSecB BgSecHex
Secondary text TextSecR TextSecG TextSecB TextSecHex
Inline-code background CodeBgR CodeBgG CodeBgB CodeBgHex
Code block background PreBgR PreBgG PreBgB PreBgHex
Table header background TableBgR TableBgG TableBgB TableBgHex

Typography & layout (double / int, in px unless noted)

Property Meaning
CodeFontSize Code font size
BorderRadius Code-block / pre corner radius
MermaidBgR MermaidBgG MermaidBgB Mermaid container background (0–255)
MermaidContainerPadding Β· MermaidContainerMargin Β· MermaidBorderRadius Mermaid container padding / margin / radius
PumlBgR PumlBgG PumlBgB PlantUML container background (0–255)
PumlContainerPadding Β· PumlContainerMargin Β· PumlBorderRadius PlantUML container padding / margin / radius
PumlDarkInvert PlantUML dark invert strength (0–1)
PlotBgR PlotBgG PlotBgB Function-plot container background (0–255)
PlotContainerPadding Β· PlotContainerMargin Β· PlotBorderRadius Function-plot container padding / margin / radius
PlotAxisColor Function-plot axis / grid / title colour (CSS colour string, code-only)

Code highlighting β€” highlight.js token colors (string, e.g. "#569cd6"):

HljsKeyword, HljsLiteral, HljsSymbol, HljsName, HljsBuiltIn, HljsType, HljsClass, HljsNumber, HljsString, HljsMetaString, HljsTitle, HljsTitleClass, HljsTitleClassInherited, HljsParams, HljsVariable, HljsTemplateVariable, HljsComment, HljsQuote, HljsAttr, HljsAttribute, HljsMeta, HljsTag, HljsSelectorAttr, HljsSelectorClass, HljsSelectorId, HljsSelectorPseudo, HljsSelectorTag, HljsBullet, HljsSection, HljsLink, HljsRegexp, HljsTemplateTag, HljsDoctag, HljsBackground, HljsForeground β€” plus diff markers HljsAdditionBg/HljsAdditionColor and HljsDeletionBg/HljsDeletionColor.

The built-in MarkdownThemeView panel covers colors, typography, and diagrams; code-highlighting tokens (Hljs*) and PlotAxisColor are configured in code (or bound in your own UI).


Working on the renderer

There is no unit test project and no CI. Verification is three environment-gated drivers in the demo app, each of which exits the process β€” enable exactly one (a guard fails fast if more than one is set):

Command Checks
AVALON_THEME_AUDIT=1 dotnet run --project AvalonMarkdown.Test.Desktop every MarkdownThemeViewModel property reaches the generated CSS, and every generated rule has a live consumer
AVALON_CAPTURE_ERRORS=1 dotnet run --project AvalonMarkdown.Test.Desktop drives the app through its phases; asserts fence dispatch and that the showcase assets load
AVALON_RENDER_CHECK=1 AVALON_RENDER_CHECK_FILE=<abs path> dotnet run --project AvalonMarkdown.Test.Desktop renders one file through the real pipeline and DOM-audits it; normally driven by render_check.py

Run without any of them for an ordinary visual check.

Authoring reference & validator

skills/avalonmarkdown-syntax/ documents the exact Markdown dialect this control renders β€” every member with worked good/bad examples, including the delimiter rules for math and the expression grammar for function plots.

references/templates.md holds copy-paste-ready source for each member. Those snippets are not written from memory: scripts/check_templates.py extracts every one of them, renders the result through the real pipeline, and fails if anything breaks.

Installing it as a Claude Code skill

The folder is a valid Claude Code skill, so it works as-is for your projects β€” if you are generating Markdown that this control will render, install it into the project where the generation happens:

mkdir -p .claude/skills
cp -r <path-to>/AvalonMarkdown/skills/avalonmarkdown-syntax .claude/skills/

Claude Code then loads it automatically and consults it before writing Markdown. It is deliberately not installed into this repository's own .claude/ β€” a skill for authoring documents is not something the repository itself needs, and committing an agent instruction file here would push it on everyone who clones.

It ships a validator that answers a question the preview cannot: will this actually render? Most failures here are silent β€” KaTeX draws its own error span without logging, a $$ block left open deletes the rest of the document, and a PlantUML syntax error comes back as a renderable error picture that displays at full size as if it were fine.

python skills/avalonmarkdown-syntax/scripts/render_check.py doc.md

Two tiers. A static pass re-derives the renderer's own dispatch rules offline and instantly. With a build present it also renders the file through the real WebView pipeline and inspects the DOM, which is the only way to know that a formula compiled or a diagram parsed.

python skills/avalonmarkdown-syntax/scripts/render_check.py --self-test

--self-test asserts every check fires against the known-bad fixtures in skills/avalonmarkdown-syntax/fixtures/ β€” a validator with no known-bad sample cannot be trusted, so the fixtures and the assertions are part of the deliverable.

Demo

The repository contains runnable sample apps (AvalonMarkdown.Test.Desktop and Android/iOS/Browser variants) that exercise both levels.

Its toolbar carries three special-member showcases alongside the general tour β€” Tables, Diagrams and Plots. Each isolates one kind of element and runs a large set of cases at it, so a regression in that member is obvious rather than buried:

  • Tables β€” alignment, escaped pipes inside code spans, math and footnotes in cells, CJK and emoji, an eight-column layout, plus three shapes that must not become tables.
  • Diagrams β€” 15 Mermaid types and 11 PlantUML types, with CJK and HTML labels, alongside a deliberately broken diagram of each kind so the failure appearance is documented next to the successes.
  • Plots β€” the full Penner/easings.net set (sine through bounce, in/out/inOut) plus non-y = f(x) graph types: parametric, polar, implicit, scatter, vectors.

They live as real .md files under AvalonMarkdown.Test.Shared/Assets/Showcase/ rather than C# string literals, so the validator checks the exact file the demo renders. The capture driver asserts all three load, since a wrong asset key fails silently into a fallback error block.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.0 83 9/13/2026
9.0.0 102 9/5/2026
8.0.0 111 8/22/2026
6.0.0 101 8/22/2026