In today's article, I'd like to talk about CodeWF.Markdown, a control I recently finished building.
It's a Markdown rendering control based on C# + Avalonia 12 + Markdig. It originated from CodeWF.AvaloniaControls, and later I extracted the Markdown-related code into a separate repository and a set of NuGet packages: rendering control, theme resources, sample apps, and tests—all organized around Markdown preview.
Its positioning is not "convert Markdown to HTML and stuff it into a WebView," but rather render the Markdown AST into an Avalonia control tree. The advantage is that desktop control capabilities like themes, fonts, selection, copying, image preview, code block toolbars, and incremental refresh can all stay within the Avalonia ecosystem.
This article doesn't just show a few interface screenshots; I ran the sample app and captured fresh materials, including static screenshots and GIF animations. Let's start with a feature tour.

Why Extract a Separate Markdown Control
When I write articles, organize documents, or create toolbox pages, I often need a local Markdown preview control. A WebView could work, but it brings issues into another ecosystem: HTML/CSS injection, script security, platform differences, copy behavior, image preview windows, and theme resource synchronization all need extra handling.
A more natural approach in Avalonia desktop apps is to turn Markdown directly into a control tree:
- Paragraphs, headings, lists, blockquotes, tables are Avalonia controls.
- Code blocks can have copy buttons, language labels, and highlighting controls.
- Images can leverage Avalonia's window capabilities for preview, zoom, rotation, and save-as.
- Themes can use
ResourceDictionaryinstead of maintaining a separate CSS. - Multiple
MarkdownViewerinstances in the same window can have different layout densities.
That's the main direction of CodeWF.Markdown: create a Markdown Viewer suitable for direct embedding in Avalonia applications, instead of wrapping another browser layer.
Current Package Lines
The repository currently has these main package lines:
CodeWF.Markdown: Full-featuredMarkdownViewerwith code highlighting, image/SVG, math formulas, multi-language resources, incremental rendering, etc.CodeWF.Markdown.Themes: Full-featured control templates and typography theme resources.CodeWF.Markdown.Lite: Lightweight Viewer, with package references only including Avalonia and Markdig, suitable for basic Markdown preview scenarios.CodeWF.Markdown.Lite.Themes: Corresponding theme resources for the Lite version.CodeWF.Markdown.Sample: Full-featured sample app.CodeWF.Markdown.Lite.Sample: Lite sample app.tests/CodeWF.Markdown.Tests: Tests for rendering model, diff service, and theme resources.
The repository is here: https://github.com/dotnet9/CodeWF.Markdown.
The screenshots correspond to repository version 12.0.3.1. I have executed locally:
dotnet build CodeWF.Markdown.slnx
dotnet test CodeWF.Markdown.slnx --no-restore
Currently, 13 test cases pass. During build, you may see a .NET preview SDK warning, but no compilation warnings or errors.
Sample App: Edit on Left, Live Preview on Right
Let's first look at the main sample app. The interface is divided into two parts: Markdown input on the left and rendered preview on the right.

The toolbar at the top has several key settings:
- Application theme: Light, Dark.
- Typography theme: Switches the accent color, heading borders, blockquote backgrounds, table styles, code area styles, etc., of the Markdown content.
- Compact layout: Tightens font size, line height, and block spacing, suitable for higher information density in tool interfaces.
- Language: Currently supports Simplified Chinese, Traditional Chinese, English, Japanese.
The left dropdown loads multiple Markdown sample files to cover basic elements, typography themes, code and tables, lists and blockquotes, image links, and incremental rendering stress scenarios.
This sample app is not intended to be a full Markdown editor, but to verify the control's behavior in a real desktop window: resizing, scrolling, theme switching, multi-language, continuous text changes in the editor, and incremental refresh in the preview—all tested together.
Common Markdown Elements
The full version uses Markdig to parse Markdown. Common elements are mostly rendered as control trees:
- Headings, paragraphs, bold, italic, strikethrough.
- Inline code and code blocks.
- Ordered lists, unordered lists, nested lists.
- Task lists.
- Blockquotes, including lists, code, and tables within blockquotes.
- Tables, links, horizontal rules.
- Footnotes, math formulas, and other extended syntax.
- HTML inline and HTML block fallback.
There is one trade-off: CodeWF.Markdown does not execute HTML. That means content like <section>, <span> in Markdown will be displayed safely as text, rather than being executed as HTML and styles like a browser. For a desktop preview control, this default behavior is more stable, especially for displaying external input or user-edited content.
Code Blocks: Highlighting, Language Labels, and Copy Buttons
Code blocks are a part of Markdown preview that is often overlooked but crucial for real-world experience.

Currently, code blocks display a language label and a copy button, with syntax highlighting via TextMateSharp. The screenshot shows JSON, C#, TypeScript, Shell, and other code blocks.
I didn't just make code blocks a plain text box, because when reading Markdown documentation, you often need to copy code; additionally, the code area background, border, font size, and button size should change along with the theme resources.
The control also exposes a CodeBlockToolRender event to allow external projects to add their own buttons to the code block tool area. For example, some applications might want to add "Run," "Copy as command," or "Send to terminal" entries on code blocks—these should not be hardcoded inside MarkdownViewer.
Tables and Long Content

Tables are not simple string concatenation; they are rendered by row, column, and cell. Header background, borders, and text color are read from theme resources.
This point may seem minor, but it's important for Chinese documentation. Many Markdown documents contain lengthy Chinese explanations, English identifiers, links, version numbers, and paths. If a table cannot naturally wrap text, the preview area can easily overflow horizontally.

The sample includes aligned tables and complex cell content to observe:
- Whether borders between headers and content are stable.
- Whether long text wraps within cells.
- Whether inline code, bold text, and links display correctly in tables.
- Whether table styles update immediately after theme switching.
Images, SVG, and Preview Window
Images in Markdown are also an independent control MarkdownImage, not simply placing an image into the document.

It supports local images, URL images, Data URIs, and SVG images.

When an image fails to load, it shows fallback text and does not interrupt the rendering of the entire Markdown document.

Clicking on an image opens a preview window:

The preview window supports:
- Zoom out, zoom in.
- 1:1 display.
- Fit to window.
- Rotate left, rotate right.
- Save as.
For SVG images, they are additionally converted to bitmaps as preview input to avoid the preview window becoming invalid when the original Viewer clears image resources. In the recent round, we also specifically handled image resource disposal: when Markdown is replaced, the control leaves the visual tree, or an image hasn't finished loading, we cancel unfinished tasks and release bitmaps.
Math Formulas and Chemical Expressions

The full version integrates Sylinko.CSharpMath.Avalonia for math formula rendering. A newly added internal MarkdownMathView ensures that the formula foreground color follows the current Markdown theme.

These details are not obvious in a light theme, but become apparent when switching to a dark theme: regular text turns light, but if the formula still uses a fixed black color, it becomes hard to read. Incorporating formulas into the Markdown theme system ensures a consistent reading experience across the whole document.
The sample document also includes chemical expression handling, which converts some LaTeX chemical commands into content more suitable for copying and plain text extraction. It's not meant to replace a professional formula editor, but to ensure that formula segments in common technical articles display correctly in desktop preview.

Typography Themes: More Than Just Changing a Color
Typography themes are a part of this control that I've focused on. They are not just a simple accent color change, but a complete set of Markdown reading resources.
You can see the theme switching effect in this GIF:

The current theme resources include these directions:
Basic- Orange Heart
- Ink Black
- Colorful Purple
- Tender Green
- Green Vibe
- Red Crimson
- Blue Firefly
- Tech Blue
- Orchid Green
- Yamabuki
- Frontend Peak
- Geek Black
- Simple
Here is the effect under the Tech Blue theme:

The dark application theme combined with the Geek Black typography theme is more suitable for code and technical notes:

Theme resources use fixed keys, for example:
<SolidColorBrush x:Key="CodeWFMarkdownAccentBrush" Color="#0F766E" />
<SolidColorBrush x:Key="CodeWFMarkdownQuoteBackgroundBrush" Color="#ECFDF5" />
<x:Double x:Key="CodeWFMarkdownParagraphLineHeight">31</x:Double>
This way, when external projects want to customize themes, they don't need to modify control code; they just need to override these resource keys. The default theme package also provides Light/Dark resource sets; when switching the Avalonia application theme, the same Markdown typography theme loads the corresponding light or dark resources.
Single Viewer Can Override Theme Locally
In a real application, there may be more than one Markdown preview area. For example, the main content on the left and review notes on the right; or the main document uses normal typography, while the summary uses compact layout.
Therefore, MarkdownViewer now supports per-control overrides for:
TypographyThemeTypographySize
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
The multi-viewer sample is specifically for verifying this scenario:

Viewer A can follow the unified settings above, while Viewer B can use "Simple + Compact" on its own. Local settings are written into the current Viewer's resource scope and do not pollute sibling Viewers.
This is very practical for desktop applications. Many applications don't just have a single "article reading page"; they embed Markdown preview in settings descriptions, version updates, AI replies, log explanations, document details, comparison views, etc. Different areas have different reading densities, so relying solely on a global theme is not sufficient.
Incremental Rendering: Replace Only Changed Areas
If every character input triggers a full rebuild of the entire Markdown document, it's fine for short text, but long documents can become sluggish.
CodeWF.Markdown implements an incremental rendering path: the control retains already rendered blocks, then after text changes, it uses a diff service to determine the change range and tries to replace only the affected blocks.
The sample app has a "Start Incremental Demo" button that automatically simulates three types of changes:
- Replace a section of content in the document.
- Insert a Chinese fragment in the middle of the body.
- Append new Markdown blocks at the end of the document.
The effect is shown below:

This logic is not for showing off; it's to align with real editing scenarios. In Chinese documentation, you often don't just change a single English word; you change an entire sentence, insert an explanation, or append a table or code block. Incremental rendering needs to handle these continuous text changes.
Of course, it doesn't force a partial refresh at the expense of correctness. If the change range is too large, affects too much structure, or the partial replacement is no longer suitable for reusing old blocks, it falls back to full rendering. For a preview control, correctness is still more important than "always partial refresh."
Selection and Copy
MarkdownViewer is a read-only preview control, but read-only doesn't mean non-interactive.
Currently, the control supports:
- Selecting rendered text.
- Copying the selection.
- Copying the entire rendered text from an empty area.
- Copying a code block individually.
One detail here: rendered text is not simply the raw Markdown. For example, tables, lists, image alt text, formulas, task lists, etc., need to be extracted into plain text suitable for copying. The MarkdownParser and shared rendering model in the repository handle part of this work.
When implementing this feature, my main concern was "Is the copied content usable?" If a user just wants to copy a description, a table, or a code block from a Markdown preview, they shouldn't be bothered by Markdown markers.
Lite Package: A Lightweight Path for Basic Preview
The full version has comprehensive features, but also more dependencies:
TextMateSharpfor code highlighting.Svg.Controls.Skia.AvaloniaandSvg.Skiafor SVG.Sylinko.CSharpMath.Avaloniafor math formulas.Lang.Avalonia.Jsonfor multilingual resources.
Some projects only need basic Markdown preview without these extensions. For this scenario, I extracted CodeWF.Markdown.Lite.
The Lite version retains:
- Headings, paragraphs, lists, task lists.
- Blockquotes, tables.
- Bitmap images.
- Plain text code blocks with copy buttons.
- Corresponding Lite theme package.
Its direct package references only include Avalonia and Markdig, suitable for projects sensitive to dependency size. If you need code highlighting, SVG, math formulas, image preview windows, etc., use the full version.
How to Integrate
For the full version, install the package:
Install-Package CodeWF.Markdown.Themes
If you prefer using .NET CLI, you can also add the package like this:
dotnet add package CodeWF.Markdown.Themes
The corresponding NuGet addresses:
If you only need the lightweight basic preview, install the Lite package line:
dotnet add package CodeWF.Markdown.Lite.Themes
Lite's corresponding NuGet address:
Project source code on GitHub: https://github.com/dotnet9/CodeWF.Markdown.
In App.axaml, include the themes:
<Application
xmlns="https://github.com/avaloniaui"
xmlns:markdown="https://codewf.com">
<Application.Styles>
<FluentTheme />
<markdown:MarkdownThemes />
</Application.Styles>
</Application>
Directly use MarkdownViewer in your page:
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:md="https://codewf.com">
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<md:MarkdownViewer
Markdown="{Binding Markdown}"
TypographyTheme="Simple"
TypographySize="Small" />
</ScrollViewer>
</UserControl>
If you don't specify TypographyTheme and TypographySize, the defaults are Basic + Normal. You can also set global defaults on MarkdownThemes.
To switch resources at runtime, call:
MarkdownThemes.OverrideTypographyResources(
app,
MarkdownTypographyThemes.BlueGlow,
MarkdownTypographySizes.Small);
You can also override within a specific window or control scope to avoid affecting the global application style.
Repository Organization
The current repository structure is roughly:
src/
CodeWF.Markdown
CodeWF.Markdown.Themes
CodeWF.Markdown.Lite
CodeWF.Markdown.Lite.Themes
CodeWF.Markdown.Sample
CodeWF.Markdown.Lite.Sample
CodeWF.Markdown.Shared
tests/
CodeWF.Markdown.Tests
CodeWF.Markdown.Shared contains shared rendering models, Markdown parsing, diff service, and other code. Both the full version, Lite, and tests can reuse this layer.
Theme resources are placed in CodeWF.Markdown.Themes and CodeWF.Markdown.Lite.Themes. This way, control code, default templates, and typography resources can be maintained separately, and external projects can choose to reference only the packages they need.
What Was Done in Recent Rounds
From the changelog, the recent versions mainly added:
- New
CodeWF.Markdown.Liteand corresponding theme packages and sample app. MarkdownVieweraddedTypographyThemeandTypographySizeproperties, supporting per-Viewer overrides.MarkdownThemesadded compact typography resources.- Sample app adjusted to Tab structure, added multi-viewer demo.
- Multilingual resources switched from Resx to JSON, distributed as NuGet content files.
- Improved image resource disposal and image preview window resource holding.
- New internal
MarkdownMathViewso math formula colors follow the current theme. - Added trimming preservation configurations for Markdown, themes, SVG, and related assemblies, improving trimmed publish compatibility.
These tasks may not be as eye-catching as "adding a big feature," but they are crucial for long-term usability of the control. Especially theme resources, image disposal, local Viewer overrides, and the Lite package line—they determine whether this control can be used in real projects, not just look good in demos.
Areas Still Open for Improvement
CodeWF.Markdown now covers my own common Markdown preview scenarios, but there is still room for further polish:
- More themes can be added, and more edge-case details unified.
- Long documents and complex tables can still be stress-tested further.
- Code highlighting language coverage can be verified more extensively.
- Image preview window shortcuts and interactions can be made more complete.
- Documentation can include more "how to integrate in a business project" snippets.
- The capability boundaries between Lite and full version should be written more clearly in the README.
- AOT, trimming, and different platform font differences can be tested more.
My current goal for this control is clear: first stabilize the common reading and preview scenarios, then gradually add advanced capabilities. Markdown controls can easily become scattered, so the package lines, theme resources, shared rendering model, and tests must be solid first.
Final Thoughts
CodeWF.Markdown is an Avalonia Markdown rendering control extracted from my own projects. Its value is not in "I can also render a few headings and lists," but in treating Markdown preview as a desktop control with serious consideration:
- Not a WebView, but an Avalonia control tree.
- Not a single style, but typography theme resources.
- Not only global settings, but per-Viewer overrides.
- Not a full rebuild on every input, but incremental rendering when possible.
- Not only a full version, but a Lite package line for basic preview.
For scenarios like desktop tools, document management, AI reply preview, changelog display, configuration descriptions, and developer toolboxes, a themeable, copyable, embeddable, and maintainable MarkdownViewer is quite valuable.
Going forward, I'll continue using it in my own tools and article workflows, adding details encountered in real scenarios. Rather than making a one-time demo, I'd rather polish it into a Markdown preview control that can be directly used in Avalonia projects.