499 GitHub stars and counting — AvaloniaUI/Avalonia.Markup.Declarative is a C# project TopGit is tracking across repositories on the platform. Provides helpers for declarative ui in C#
Snapshot summary built from the project's own GitHub metadata — there's no written TopGit review yet. The page will update automatically when a full review is published.
WHY NO REVIEW YET
TopGit writes full reviews for the most-starred, most-requested repositories. This page is a snapshot until then — see the READ ME tab for the original README in full.
Important: This repository is community driven, not officially supported by avalonia team and is not part of the official Avalonia project—it's only a proof of concept demonstrating how markup can be written purely in C#. For real-world projects use Avalonia's supported XAML approach.
Avalonia.Markup.Declarative
Write Avalonia UI with C# like a Boss 🗿
Avalonia.Markup.Declarative is a C#-first authoring layer over Avalonia controls. The current API is compiled-binding-first and source-generator-driven, with public patterns intentionally aligned with Avalonia's DataContext, binding, style, and selector model.
real projects that using Avalonia.Markup.Declarative
https://github.com/gritsenko/pix2d - Pix2d graphic editor for indie developers
feel free to add your project
Installation
Add the Avalonia.Markup.Declarative NuGet package to your project
Project Template
You can easily create a new project from the command line using the official template:
dotnet new install Declarative.Avalonia.Templates
dotnet new avalonia-declarative -n MyApp
Use a self-contained declarative component when the view and its reactive state belong to the same feature. Add CommunityToolkit.Mvvm to the app project and keep the component-local state in a nested ObservableObject.
using Avalonia.Data;
using CommunityToolkit.Mvvm.ComponentModel;
public class CounterComponent() : ViewBase<CounterComponent.State>(new State())
{
public sealed partial class State : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CounterLabel))]
public partial decimal? Counter { get; set; } = 0;
[ObservableProperty]
public partial string StatusText { get; set; } = "Hello world";
public string CounterLabel => $"Counter: {Counter}";
}
protected override object Build(State state) =>
new StackPanel()
.Children(
new TextBlock()
.Text(state, x => x.StatusText),
new TextBlock()
.Text(state, x => x.CounterLabel),
new NumericUpDown()
.Value(state, x => x.Counter, BindingMode.TwoWay),
new Button()
.Content("Increment")
.OnClick(_ => state.Counter++)
);
}
To compose constructor-injected views, prefer ViewFactory.Create<T>(). If you use DI, register UseComponentControlFactory(...) on AppBuilder.
MVVM Pattern implementation
Use ViewBase<TViewModel> when you want a classic Avalonia or WPF-style view model. Generated setters expose compiled-binding overloads, so the binding syntax stays close to native Avalonia.
using Avalonia.Data;
public class MainView() : ViewBase<MainViewModel>(new MainViewModel())
{
protected override object Build(MainViewModel vm) =>
new StackPanel()
.Children(
new TextBox()
.Text(vm, x => x.Message, BindingMode.TwoWay),
new TextBlock()
.Text(vm, x => x.Message),
new Button()
.Content("Reset")
.OnClick(_ => vm.Message = string.Empty)
);
}
If you assign DataContext from the outside, the same generated setters also support DataContext-relative compiled bindings such as new TextBlock().Text<MainViewModel>(x => x.Message);.
Generated compiled-binding setters also apply automatic conversion for common primitive and nullable mismatches, so bindings like new Slider().Value(vm, x => x.Counter, BindingMode.TwoWay) work when Counter is int, and new CheckBox().IsChecked(vm, x => x.Enabled) work when Enabled is bool. Prefer plain member access such as x => x.Counter: a numeric-conversion cast like x => (double)x.Counter is both unnecessary (the auto-converter handles it) and unsupported — Avalonia's expression parser rejects value-converting Convert nodes. Type casts that navigate to a member of a derived type, such as x => ((DerivedType)x).Property, are supported. For lossy numeric TwoWay conversions, convert-back truncates toward zero.
Passing a ready-made binding
When you need the full binding feature set — reflection bindings (Binding), a pre-built compiled binding, a TemplateBinding, a MultiBinding, or a relative-source/element-name binding — every generated property, attached-property and style setter also exposes an overload that accepts a BindingBase directly:
using Avalonia.Data;
new TextBlock()
.Text(new Binding("ReflectionProperty")) // DataContext-relative reflection binding
.Foreground(new Binding("Theme.Accent") { Source = appState }); // explicit source
new TextBlock()
.Text(CompiledBinding.Create<MyViewModel, string>(x => x.Title, source: vm));
// attached properties and styles get the same overload
new Border().Grid_Row(new Binding(nameof(vm.Row)) { Source = vm });
new Style<TextBlock>().Text(new Binding(nameof(vm.Name)));
This is the escape hatch for anything the strongly-typed x => x.Member expression overloads don't cover (custom converters passed on the binding, RelativeSource, ElementName, string format, multi-value bindings, etc.). The same thing is available on any AvaloniaProperty via control.BindValue(TextBlock.TextProperty, binding).
Hot reload support
ViewBase supports .NET 6.0+ hot reload.
Keeping declarative views in an assembly without XAML can still produce the smoothest hot reload experience.
Current Avalonia and .NET toolchains are much better at mixing AXAML and C# markup in the same application, so the limitation is much smaller than it used to be.
The optional, dev-onlyDeclarative.Avalonia.AgentTools package runs an in-process MCP (Model
Context Protocol) server on loopback in debug builds, so an AI agent iterating on your UI can see and
drive the running app — closing the edit → hot-reload → verify loop without a human relaying what the
window looks like.
using Declarative.Avalonia.AgentTools; // under #if DEBUG
AppBuilder.Configure<App>()
.UsePlatformDetect()
#if DEBUG
.UseAgentInspector() // loopback MCP server on 127.0.0.1:5599
#endif
.SetupWithLifetime(lifetime);
It exposes screenshots (with before/after pixel-diffing), the visual tree with bounds and a single
absolute client-DIP coordinate frame (abs/center) shared by every tool, per-control layout reports,
an automated layout audit, property / property-source / view-model inspection, pixel↔control hit-testing
(with a "how to drive this control" hint), and recent build/binding/runtime errors. An opt-in tier
(EnableInteraction) also drives the app — real synthesized pointer/keyboard input (tap, drag,
pointer_*, that work even on custom controls with no automation peer), click, type, select, resize,
switch theme, open a closed popup — plus an escape hatch (with structured, actionable errors) to set a
view-model property or run a command directly to reach awkward states.
Keep the call under #if DEBUG: the package pulls in a web stack and a remote-control surface and must not
ship in Release; it binds to loopback only. See docs/agent-tools.md for the full
guide.
Enable the MCP in your agent
The inspector is a streamable-HTTP MCP server on http://127.0.0.1:5599, so every agent points at the
same URL. Run the app under dotnet watch so the agent's edits hot-reload into the process it inspects.
Claude Code — claude mcp add --transport http avalonia-agent-inspector http://127.0.0.1:5599, or a
project .mcp.json:
Codex — an [mcp_servers.*] table in ~/.codex/config.toml (a url makes it streamable-HTTP; enable
the RMCP client once with [features] → experimental_use_rmcp_client = true):
Make sure that the path to the source generator project is correct relative to your project.
Note: If you are using this library as a NuGet package, the source generator is included automatically.
External libraries support
Framework extensions are generated automatically for the supported Avalonia assemblies that your project references. To generate extensions for a third-party library, add an assembly attribute that points to any type from that assembly:
using Avalonia.Markup.Declarative;
using ReactiveUI.Avalonia;
[assembly: GenerateMarkupExtensionsForAssembly(typeof(RoutedViewHost))]
No standalone tool installation or manual avalonia-amd-gen step is required anymore.
Does AvaloniaUI/Avalonia.Markup.Declarative have any tags?
TopGit's last sync did not record any GitHub topics for AvaloniaUI/Avalonia.Markup.Declarative. GitHub topics appear in the right sidebar of a repository page; that's the authoritative place to check.
How active is development on AvaloniaUI/Avalonia.Markup.Declarative?
The most recent commit recorded on AvaloniaUI/Avalonia.Markup.Declarative was 17 days ago, based on the GitHub push timestamp. The repository has 27 forks — one of the better signals of community interest.
How many stars does AvaloniaUI/Avalonia.Markup.Declarative have?
AvaloniaUI/Avalonia.Markup.Declarative has 499 GitHub stars — refresh the page for the live number, or check github.com/AvaloniaUI/Avalonia.Markup.Declarative. TopGit mirrors GitHub's count but does not claim minute-by-minute accuracy.
What language is AvaloniaUI/Avalonia.Markup.Declarative written in?
AvaloniaUI/Avalonia.Markup.Declarative is written primarily in C#. GitHub's language field is based on the largest share of bytes in the default branch.
Where do I read more about AvaloniaUI/Avalonia.Markup.Declarative?
This TopGit page is a snapshot — the READ ME tab shows the project's own README content (links stripped, images preserved). The GitHub repository at github.com/AvaloniaUI/Avalonia.Markup.Declarative is the definitive source.
Read full README in the tab above.
Curious whether Avalonia.Markup.Declarative is right for you?
Let ChatGPT, Claude, or Perplexity look into it — click below and see what AI actually says about Avalonia.Markup.Declarative.