Custom Avalonia Themes developed by Devolutions
➡️ MacOS Theme
➡️ Linux Theme
Avalonia 12 line. Packages
2026.6.17-avalonia12and later are on the Avalonia 12 line. The first stable Avalonia 12 release is2026.6.23, and2026.6.16is the last stable release compatible with Avalonia 11.Maintenance-only fixes for the Avalonia 11 line may still be published as
2026.6.16.xfor a limited time. See the Avalonia 11 -> 12 breaking changes for upstream migration guidance.
Contributors can use the SampleApp to test, debug and document styles for the various controls under each theme.
The theme selector groups themes under Linux, MacOS, and Windows. Avalonia Fluent and Avalonia Simple appear first without a group header. Its popup grows to fit the themes, limited only by the available screen space.
The SampleApp attaches the Avalonia Dev Tools for inspecting controls (open with F12).
We will soon start to add styles for at least some Avalonia Accelerate controls, starting with TreeDataGrid in the DevExpress theme.
To view and test Accelerate-licensed controls in the SampleApp:
- Create a
.envfile in the repository root. - Add your license key:
AVALONIA_LICENSE_KEY=your_key_here. - Rebuild the solution.
Note: If the controls don't appear or you see build errors, you may need to force a NuGet restore or invalidate your IDE caches (e.g., File > Invalidate Caches in Rider) to update the conditional package dependencies.
There is limited visual regression testing available. Native themes use tracked images in tests/Devolutions.AvaloniaControls.VisualTests/Screenshots/Baseline/{CurrentOS}/{Theme}; non-native themes use personal images in Screenshots/LocalBaselines/{Theme}. Images from other operating systems are never compared. Personal baselines are gitignored and belong to this machine and worktree. Diffs for failing tests are saved to tests/Devolutions.AvaloniaControls.VisualTests/Screenshots/Test-Diffs.
Screenshots are captured at a fixed width (1200) with auto-calculated content height (capped at 3000) to cover below-the-fold examples without requiring manual page-by-page configuration.
- Interactive behaviours (e.g. pointerOver, popUpOpen, focus, etc.) are not tested
- Accelerate controls that depend on a licence (e.g. TreeDataGrid) are not tested
Only screenshots from a theme's target platform are canonical and required in PRs:
| Theme | Canonical platform |
|---|---|
| DevExpress, WinUiClassic, WinUiMica | Windows |
| MacClassic, LiquidGlass | macOS |
| Linux/Yaru | Linux |
Cross-platform screenshots are a local developer convenience, not required PR coverage. In particular, Windows WinUI changes do not require macOS or Linux baseline images. Adding a new control still requires appropriate target-platform visual coverage.
Pages marked "same as reference" in the catalog compare the two themes' rendered output directly instead of storing a separate PNG for the delegating variant.
Before CI owns baseline generation, native themes always compare with tracked images; stale personal copies of those themes are ignored. Non-native themes compare only with personal images. For example, on macOS, MacClassic/LiquidGlass use tracked images, while DevExpress/WinUI/Linux use personal images.
A missing image from the appropriate set fails with Missing baseline. A differing image fails with Visual regression. There is no missing-local failure for a native theme with a valid tracked image.
Personal images live directly under LocalBaselines/{Theme}/, with no OS subdirectory. When migrating an older local tree, remove the other OS trees before moving your current OS's theme folders up one level: Linux is both an OS directory name and a theme name. Do not reuse other OS images. Each machine generates its own personal set.
Temporary local publishing workflow (until GitHub runners own canonical generation):
--update-baselinesapproves intentional changes: native themes write only tracked images; non-native themes write only personal images. It does not create duplicate native personal images.- Review and commit only intentional target-platform changes in
Screenshots/Baseline/. The rest remain in gitignoredLocalBaselines/. - Initialize a new worktree with
./devtest visual --initialize-local-baselines, on the clean starting revision before making changes. This is capture-only: it writes non-native personal screenshots without comparing images or running WinUI reference-theme identity checks. Tracked images are never written or tested in this operation. - If
LocalBaselinesis non-empty, the command warns and requires typingybefore overwriting selected non-native images. Any other answer or end of input cancels before capture. Unselected images remain unchanged; use the unfiltered command for a full set. Agents must surface this overwrite confirmation to the user and must not answer it automatically or pipeywithout explicit overwrite approval. - Agents always ask once at the start of a new session whether to initialize the full development set, even if the planned task is non-visual. Do not generate from an already modified tree without explicit user approval.
- Personal images are worktree-specific: do not automatically copy or share them via worktree setup. Existing images are not refreshed automatically after a rebase or merge; review intentional changes before updating them.
The planned GitHub workflow will replace this temporary local publishing step. See the CI proposal.
Note: We're showing single quotes here, since in interactive
bash/zsh(Linux/Mac),!triggers history expansion, so the following term is interpreted as a history variable, unless the filter string is single-quoted ('...') instead of double-quoted. However, on Windows only PowerShell supports the single quotes - so you might want to get used to double quotes if you're always on Windows.
dotnet test- runs all testsdotnet test --filter 'DisplayName~VisualRegressionTests'- runs only visual regression testsdotnet test --filter 'DisplayName!~VisualRegressionTests'- runs only non-visual testsdotnet test --filter 'DisplayName~DevExpress'- runs tests for all controls implemented in DevExpressdotnet test --filter 'DisplayName~Button'- runs tests for Button under each of the themes it's implemented indotnet test --list-tests- lists all test cases
The xUnit/VSTest filter syntax uses & for AND, | for OR and ! for NOT.
dotnet test --filter 'DisplayName~Button&DisplayName~DevExpress'- runs only tests whose display name contains bothButtonandDevExpressdotnet test --filter 'DisplayName~ContextMenu|DisplayName~MenuFlyout'- runs tests containing eitherContextMenuorMenuFlyoutdotnet test --filter 'DisplayName~Button&DisplayName!~HyperlinkButton'- runs tests containingButtonbut excludingHyperlinkButton
🆕 Shorthand command & cleaner output:
./devtest(macOS/Linux/Git Bash) or.\devtest(Windows PowerShell/CMD) - runs all tests with a succinct summary../devtest visual- runs all tests inDevolutions.AvaloniaControls.VisualTests../devtest nonvisual- runs all tests inDevolutions.AvaloniaControls.Tests../devtest functional- alias fornonvisual../devtest --filter EditableCombo- shorthand for (DisplayName~EditableCombo).
The wrapper prints a visual regression summary for screenshot mismatches and missing baselines, including the theme, page, variant, and output path. Reference-theme equality failures also appear in this table, with one row per mismatching light/dark variant.
Updating baseline screenshots when changes are intentional:
./devtest visual --initialize-local-baselines- captures non-native personal images without comparisons; asks before overwriting a non-empty personal set../devtest visual --update-baselines- updates tracked native images and personal non-native images for intentional changes../devtest --update-baselines- runs all projects with baseline updates enabled.- macOS/Linux:
UPDATE_BASELINES=true dotnet test [filters] - Windows (PowerShell):
$env:UPDATE_BASELINES="true"; dotnet test [filters]; Remove-Item env:UPDATE_BASELINES - Windows (Command Prompt):
set UPDATE_BASELINES=true && dotnet test [filters] && set UPDATE_BASELINES=
🤍🖤 All tests are run for light & dark mode However to keep things reasonably quick, the dark mode test only runs if the light mode test for the same control has passed. This can lead to missed issues, when both versions have different problems. (If in doubt, update, then delete the new dark baseline shot, and run the test again (light will no longer fail, and you can see what happens in dark))
If you're an AI assistant (like GitHub Copilot or Claude Code) working on this repository, comprehensive guidelines are available in .claude/CLAUDE.md.
This includes:
- Repository structure and architecture
- Development workflows and commands
- Coding standards and best practices
- Version control rules and commit guidelines
- Custom commands for theme switching and development
