A Markdig-powered markdown viewer control for Avalonia UI v12. Drop MarkdownViewer into any Avalonia window or panel to render rich markdown — headings, code blocks, tables, task lists, links, images, and more — using native Avalonia controls with a fully customisable theme.
dotnet add package MarkView.AvaloniaInclude the default theme and add MarkdownViewer to your XAML:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mv="using:MarkView.Avalonia">
<Window.Styles>
<StyleInclude Source="avares://MarkView.Avalonia/Themes/MarkdownTheme.axaml" />
</Window.Styles>
<mv:MarkdownViewer Markdown="# Hello, MarkView!" />
</Window>viewer.BaseUri = new Uri("https://raw.githubusercontent.com/Kryptos-FR/MarkView.Avalonia/main/");
viewer.Markdown = markdownText; // relative image paths resolved against BaseUriExternal links open automatically via the platform's launcher (OS browser on desktop,
an intent on mobile, a new tab on WebAssembly) — no code required. LinkClicked is an
Avalonia routed event (bubbles up), raised before that default launch happens;
subscribe to override it for specific links by setting e.Handled = true:
viewer.LinkClicked += (_, e) =>
{
// e.Url contains the clicked URL
if (e.Url.EndsWith(".md"))
{
viewer.Source = new Uri(e.Url); // render in-place instead of opening externally
e.Handled = true;
}
};LinkClickedEvent is an Avalonia routed event (bubbles up) — subscribe globally once at app startup to handle all viewers:
// App.axaml.cs — applies to every MarkdownViewer in the application
MarkdownViewer.LinkClickedEvent.AddClassHandler<MarkdownViewer>((_, e) =>
{
// ...
});Set AreTaskListsInteractive to make - [ ] / - [x] items clickable (off by default, so
read-only viewers keep the static ☐/☑ glyph). The viewer only toggles the checkbox; it never edits
its own markdown — handle TaskToggled to write the change back to your source:
viewer.AreTaskListsInteractive = true;
viewer.TaskToggled += (_, e) =>
{
// e.SourceStart / e.SourceLength locate the "[ ]" or "[x]" marker in viewer.Markdown
var source = viewer.Markdown!;
viewer.Markdown = string.Concat(
source.AsSpan(0, e.SourceStart),
e.IsChecked ? "[x]" : "[ ]",
source.AsSpan(e.SourceStart + e.SourceLength));
};See Interactive task lists for details.
viewer.ScrollToAnchor("my-heading"); // scrolls to the heading's anchor positionCode blocks show their language and a copy button above the code. Turn either off per viewer or for the whole application with a style:
<mv:MarkdownViewer ShowCodeBlockLanguage="False" ShowCodeBlockCopyButton="False" />Line numbers, wrapping long lines, and a maximum height past which a block scrolls inside itself are opt-in:
<mv:MarkdownViewer ShowCodeBlockLineNumbers="True" WrapCodeBlockLines="True" CodeBlockMaxHeight="400" />See Code block toolbar for details.
Render the fenced code blocks of a language as your own control, without writing a Markdig renderer. The factory gets the block's content and its info string:
viewer.UseFenceRenderer("chart", (code, info) => new MyChart(code)); // ```chart blocksSee Custom fenced blocks for details.
viewer.Pipeline = new MarkdownPipelineBuilder()
.UseGitHubExtensions() // the viewer's default pipeline
.UseAbbreviations()
.UseFigures()
.Build();Convenience methods configure a pipeline with a single opt-in feature:
viewer.UseAbbreviations();
viewer.UseAlertBlocks();
viewer.UseCitations();
viewer.UseFigures();
viewer.UseFootnotes();
viewer.UseHardlineBreaks();
viewer.UseMediaLinks();To combine several opt-in features, build the pipeline explicitly (see above).
MarkdownViewerDefaults lets you set a pipeline and/or extensions once so that every MarkdownViewer in the application inherits them automatically — no need to configure each instance:
// App.axaml.cs
MarkdownViewerDefaults.Pipeline = new MarkdownPipelineBuilder()
.UseGitHubExtensions()
.UseExtendedAlertBlocks()
.Build();
MarkdownViewerDefaults.Extensions.AddTextMateHighlighting();
MarkdownViewerDefaults.Extensions.AddSvg();
MarkdownViewerDefaults.Extensions.AddMermaid();
MarkdownViewerDefaults.Extensions.AddFenceRenderer("chart", (code, info) => new MyChart(code));Per-instance Pipeline and Extensions take precedence over the defaults; an extension object shared between the defaults list and an instance list is only registered once per render.
| Feature | Notes |
|---|---|
| Headings H1–H6 | CommonMark |
| Bold, italic | CommonMark |
| Inline code | CommonMark |
| Fenced code blocks | CommonMark; any language can render as your own control (custom fenced blocks) |
| Blockquotes | CommonMark |
| Ordered and unordered lists | CommonMark, tight and loose |
| Links and autolinks | CommonMark + extension |
| Images | CommonMark, remote URLs loaded async |
| Image sizing |  — inline pixel dimensions, e.g. =80x80 |
| Thematic breaks | CommonMark |
| Hard line breaks | CommonMark (\ or two spaces) |
HTML <br> / <br /> |
Rendered as line break |
| Feature | Syntax |
|---|---|
| Strikethrough | ~~text~~ or ~text~, as on GitHub |
| Task lists | - [x] item (clickable with AreTaskListsInteractive) |
| Pipe tables | GFM-style | col | col | |
| Autolinks | bare https://… URLs |
| Emoji shortcodes | :rocket: → 🚀 (ASCII smileys like :) are intentionally left as plain text) |
| GitHub alert blocks | > [!NOTE] etc. |
| Footnotes | [^1] |
| CJK-friendly emphasis | parser-only fix for emphasis next to Chinese/Japanese/Korean (CJK) punctuation |
| YAML front matter | --- metadata block at the top of a document is parsed and hidden |
| Collapsible details | HTML <details> / <summary>, as on GitHub (also UseHtmlDetails() on its own) |
UseSupportedExtensions(), the previous default, enables the same features except alerts, footnotes and details, and adds subscript (~text~), superscript (^text^), underline (++text++), highlight (==text==) and grid tables.
Subscript is not available on top of UseGitHubExtensions(): there ~text~ is always strikethrough, as on GitHub, even with UseEmphasisExtras(). For subscript, build the pipeline from UseSupportedExtensions() and add UseAlertBlocks() / UseFootnotes() if needed. See Markdig Pipeline.
These require adding .UseXxx() to the pipeline (see Extension Methods above):
| Feature | Activation | Syntax |
|---|---|---|
| Abbreviations | UseAbbreviations() |
*[HTML]: … |
| Foldable alerts and custom titles | UseExtendedAlertBlocks() (also enabled by viewer.UseAlertBlocks()) |
Obsidian-style > [!NOTE] Custom title, foldable > [!NOTE]- (collapsed) / > [!NOTE]+ (expanded) |
| Superscript, underline, highlight | UseEmphasisExtras() |
^text^, ++text++, ==text== |
| Grid tables | UseGridTables() |
RST-style grid tables |
| Citations | UseCitations() |
""quoted text"" |
| Figures | UseFigures() |
^^^ / ^^^ caption |
| Hardline breaks | UseHardlineBreaks() |
every soft line break renders as a hard break |
| YouTube embeds | UseMediaLinks() |
 |
| Definition lists | UseDefinitionLists() |
Term followed by : definition |
| Custom containers | UseCustomContainers() |
:::name … ::: blocks, ::text:: inline |
| Footers | UseFooters() |
^^ footer text |
| Smart punctuation | UseSmartyPants() |
"quotes", --, ---, ..., <<…>> become “ ” – — … « » |
| Letter and roman numeral lists | UseListExtras() |
a., A., i., I. |
| Style classes from attributes | UseGenericAttributes() (call it last) |
{.name} adds name to the element's style classes |
The last six are Markdig's own pipeline methods; MarkView.Avalonia renders what they parse.
MarkView.Avalonia ships optional NuGet packages that add richer rendering capabilities. Each package implements IMarkViewExtension and is activated via a convenience method on MarkdownViewer, or globally via MarkdownViewerDefaults.Extensions.AddXxx().
Adds TextMate grammar-based syntax highlighting to fenced code blocks.
dotnet add package MarkView.Avalonia.SyntaxHighlightingviewer.UseTextMateHighlighting(); // DarkPlus / LightPlus by default
// or with custom themes:
viewer.UseTextMateHighlighting(darkTheme: ThemeName.Monokai, lightTheme: ThemeName.QuietLight);The extension replaces the built-in CodeBlockRenderer with TextMateCodeBlockRenderer, which tokenises each line and emits coloured Run elements. Unsupported languages fall back to the default monochrome rendering automatically.
Colours update in-place when the user switches between light and dark themes — no document rebuild or scroll reset.
Available ThemeName values are defined by TextMateSharp.Grammars: DarkPlus, LightPlus, Monokai, SolarizedDark, SolarizedLight, and more.
Renders SVG images embedded in markdown (), including data:image/svg+xml data URIs and badge-service URLs that return SVG without a .svg extension.
dotnet add package MarkView.Avalonia.Svgviewer.UseSvg();The extension inserts SvgImageLoader at the front of the image loader chain. Regular raster images continue to load via the built-in HTTP fallback.
Renders $...$ and \(...\) (inline) and $$...$$ and \[...\] (display) LaTeX math using CSharpMath's SkiaSharp renderer — pure .NET, no browser or WebView required. The bracket delimiters, which LLMs commonly emit, can be turned off with viewer.UseMath(bracketDelimiters: false).
dotnet add package MarkView.Avalonia.Mathviewer.UseMath();Renders fenced mermaid code blocks as SVG diagrams using the Mermaider library (pure .NET, no browser required). Works on all platforms including Linux. Diagrams re-render automatically when the user switches between light and dark themes.
dotnet add package MarkView.Avalonia.Mermaidviewer.UseMermaid();Markdown syntax:
```mermaid
graph TD
A[Start] --> B{Decision}
B -- Yes --> C[End]
B -- No --> A
```All four can be stacked:
viewer
.UseTextMateHighlighting()
.UseSvg()
.UseMermaid()
.UseMath();Extensions are applied in the order they are added to viewer.Extensions. Each extension's Register method is called once per render pass, before the Markdig pipeline is set up.
Implement IMarkViewExtension from the core package:
using MarkView.Avalonia.Extensions;
using MarkView.Avalonia.Rendering;
public class MyExtension : IMarkViewExtension
{
public void Register(AvaloniaRenderer renderer)
{
// swap a renderer, add an image loader, or set a code highlighter
renderer.ObjectRenderers.ReplaceOrAdd<CodeBlockRenderer>(new MyCodeBlockRenderer());
}
}
viewer.Extensions.Add(new MyExtension());Include MarkdownTheme.axaml for default styles. Override any style class in your own Styles to customise appearance:
| Style class | Applied to | Controls |
|---|---|---|
markdown-h1 … markdown-h6 |
Headings | TextBlock |
markdown-paragraph |
Paragraphs | TextBlock |
markdown-code-block |
Code blocks | Border |
markdown-code-container |
Code block with its toolbar | StackPanel |
markdown-code-header |
Code block toolbar | Border |
markdown-code-language |
Code block language label | TextBlock |
markdown-code-copy |
Code block copy button | Button |
markdown-code-line-numbers |
Code block line numbers | TextBlock |
markdown-code-scroll |
Scrolls a code block taller than CodeBlockMaxHeight |
ScrollViewer |
markdown-custom-fence |
Control a fence renderer returned (UseFenceRenderer) |
Border |
markdown-fence-<language> |
The same, by fence language in lower case | Border |
markdown-code-inline |
Inline code | Border |
markdown-blockquote |
Blockquotes | Border |
markdown-list |
Lists | StackPanel |
markdown-list-marker |
List item bullet, number or task-list glyph | TextBlock |
markdown-task-list |
Task-list check glyph (☐/☑) | TextBlock |
markdown-task-toggle |
Clickable task-list checkbox (AreTaskListsInteractive) |
ToggleButton |
markdown-thematic-break |
Horizontal rules | Separator |
markdown-image |
Images | Image |
markdown-link |
Hyperlinks | HyperlinkButton |
markdown-table |
Tables | Grid |
markdown-table-cell |
Table cells | Border |
markdown-table-header |
Header cells | Border |
markdown-marked |
Highlighted text (==…==) |
Span |
markdown-alert |
Alert block container | Border |
markdown-alert-note … markdown-alert-caution |
Per-kind border colour | Border |
markdown-alert-header |
Alert kind label or custom title | TextBlock |
markdown-alert-collapsible |
Foldable alert (:expanded while open) |
Border |
markdown-alert-chevron |
Fold indicator in a foldable alert's header | Run |
markdown-figure |
Figure container | Border |
markdown-figure-caption |
Figure caption | TextBlock |
markdown-abbr |
Abbreviation with tooltip | TextBlock |
markdown-footnote-ref |
Footnote reference link | HyperlinkButton |
markdown-footnote-group |
Footnote definition list | StackPanel |
markdown-footnote-item |
Individual footnote row | Grid |
markdown-definition-list |
Definition list | StackPanel |
markdown-definition-term |
Defined term | TextBlock |
markdown-definition |
Definition (indented) | StackPanel |
markdown-custom-container |
Custom container (:::name, also gets name as a class) |
Border |
markdown-custom-container-title |
Custom container title | TextBlock |
markdown-custom-inline |
Inline custom container (::text::) |
Span |
markdown-footer |
Footer (^^) |
StackPanel |
Extension packages may ship their own theme file with additional overridable colours — check the extension's README for its StyleInclude path and resource keys.
Example — increase heading size and add a bottom border:
<Style Selector="TextBlock.markdown-h1">
<Setter Property="FontSize" Value="36" />
<Setter Property="Foreground" Value="#1A1A2E" />
<Setter Property="Margin" Value="0,12,0,6" />
</Style>MarkdownViewer supports full document-wide text selection:
- Click + drag to select a range.
Ctrl+Ato select all text.Ctrl+Cto copy the selection to the clipboard.
Programmatic API:
viewer.SelectAll();
viewer.ClearSelection();
string text = viewer.GetSelectedText();
await viewer.CopyToClipboardAsync();A selection that spans an image copies the image's alt text in its place; math formulas and Mermaid diagrams copy their source. See Text Selection for details.
| Limitation | Detail |
|---|---|
| Anchor scroll is instant | BringIntoView() jumps without animation. Smooth scrolling is a future improvement. |
All five packages are released together under the same version, 12.MAJOR.MINOR:
| Part | Meaning |
|---|---|
12 |
The Avalonia major version the packages are built for. It changes only with Avalonia's, so MarkView for Avalonia 13 will start at 13.0.0. |
MAJOR |
Incremented for breaking changes: a removed or renamed API, or a change of behaviour that needs code changes. |
MINOR |
Incremented for every other release: new features and fixes. |
NuGet and update tools such as Dependabot read the version as MAJOR.MINOR.PATCH, so they treat a breaking release (12.3 → 12.4) as a minor update. To receive only compatible updates, use a floating version that fixes the second number, such as 12.4.*, rather than 12.*. The release notes of each breaking release list what changed and how to update.
MIT © Nicolas Musset