Use when implementing the MVVM pattern in Avalonia, including ViewModels, INotifyPropertyChanged, ICommand, ReactiveUI, CommunityToolkit.Mvvm, or ViewModel-first navigation.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add linuxdevel/Avalonia-skills --skill avalonia-mvvm --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Avalonia Mvvm?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/linuxdevel-avalonia-mvvm)More formats (shields.io, HTML) on the badges page.
---
name: avalonia-mvvm
description: Use when implementing the MVVM pattern in Avalonia, including ViewModels, INotifyPropertyChanged, ICommand, ReactiveUI, CommunityToolkit.Mvvm, or ViewModel-first navigation.
---
# Avalonia MVVM
## Overview
MVVM: Model (data/business), View (.axaml), ViewModel (presentation logic, bindable state). ViewModel knows nothing about View. Views bind to ViewModel properties and commands.
## INotifyPropertyChanged (Manual)
```csharp
public class PersonViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get => _name;
set { _name = value; PropertyChanged?.Invoke(this, new(nameof(Name))); }
}
public event PropertyChangedEventHandler? PropertyChanged;
}
```
## CommunityToolkit.Mvvm (Recommended)
Source-generator based, no reflection, clean code.
```csharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private string _name = "";
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private bool _isDirty;
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task SaveAsync()
{
// save logic
}
private bool CanSave() => IsDirty && !string.IsNullOrEmpty(Name);
}
```
NuGet: `CommunityToolkit.Mvvm`
Generated: `Name` property with change notification, `NameChanged` partial method hook, `SaveCommand` as `AsyncRelayCommand`.
## ReactiveUI (Alternative)
```csharp
using ReactiveUI;
public class MainViewModel : ReactiveObject
{
private string _name = "";
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public MainViewModel()
{
var canSave = this.WhenAnyValue(x => x.Name, n => !string.IsNullOrEmpty(n));
SaveCommand = ReactiveCommand.CreateFromTask(SaveAsync, canSave);
}
private async Task SaveAsync() { /* ... */ }
}
```
NuGet: `Avalonia.ReactiveUI`
App setup: `.UseReactiveUI()` in Program.cs
## ICommand (Manual)
```csharp
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool>? _canExecute;
public RelayCommand(Action execute, Func<bool>? canExecute = null)
{ _execute = execute; _canExecute = canExecute; }
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? p) => _canExecute?.Invoke() ?? true;
public void Execute(object? p) => _execute();
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
```
## ViewLocator Pattern
Auto-maps ViewModels → Views by naming convention (`FooViewModel` → `FooView`):
```csharp
public class ViewLocator : IDataTemplate
{
public Control? Build(object? data)
{
if (data is null) return null;
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
return type != null
? (Control)Activator.CreateInstance(type)!
: new TextBlock { Text = $"Not Found: {name}" };
}
public bool Match(object? data) => data is ViewModelBase;
}
```
Register in App.axaml:
```xml
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
```
## ViewModel-First Navigation
Pattern: Router/NavigationViewModel holds current page ViewModel, ContentControl displays it:
```xml
<ContentControl Content="{Binding CurrentPage}"/>
```
```csharp
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private ViewModelBase _currentPage = new HomeViewModel();
public void NavigateTo(ViewModelBase page) => CurrentPage = page;
}
```
## Design-Time DataContext
```xml
<Window xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance vm:MainViewModel, IsDesignTimeCreatable=True}">
```
## ObservableCollection for Lists
```csharp
public ObservableCollection<PersonViewModel> People { get; } = new();
```
Use `ObservableCollection<T>` (not `List<T>`) so UI updates on add/remove.
## Community Libraries
| Library | NuGet / Repo | Purpose |
|---|---|---|
| CommunityToolkit.Mvvm | `CommunityToolkit.Mvvm` | Source-generator MVVM (ObservableObject, RelayCommand) — recommended |
| ReactiveUI | `Avalonia.ReactiveUI` | Reactive MVVM with Rx.NET observables |
| Prism | `Prism.Avalonia` | Full MVVM framework with regions, modules, DI, dialogs |
| Epoxy | `Epoxy.Avalonia` | Lightweight multi-platform MVVM |
| FuncUI | `Avalonia.FuncUI` | F# MVU pattern with view DSL |
| ReactiveElmish.Avalonia | GitHub: JordanMarr/ReactiveElmish.Avalonia | F# MVU with XAML views |
| Stylet.Avalonia | `Stylet.Avalonia` | Lightweight ViewModel-first MVVM (Caliburn.Micro-inspired) |
| HanumanInstitute.MvvmDialogs | `HanumanInstitute.MvvmDialogs.Avalonia` | Open dialogs from ViewModel in pure MVVM |
| ReactiveValidation | `ReactiveValidation` | Fluent-style property validation with UI messages |
| Markup Declarative | `Avalonia.Markup.Declarative` | Declarative C# UI and MVU helpers |
| Lemon.ModuleNavigation | GitHub: NeverMorewd/Lemon.ModuleNavigation | .NET Generic Host + module navigation |
| AsyncNavigation | GitHub: NeverMorewd/AsyncNavigation | Async navigation via Microsoft.Extensions.DI |
## Common Mistakes
- Using `List<T>` instead of `ObservableCollection<T>` — list changes not reflected in UI
- Not calling `PropertyChanged` with exact property name — use `nameof()`
- Doing UI work in ViewModel (creating controls, accessing `DataContext`)
- Async command without `AsyncRelayCommand` — exceptions swallowed, UI blocks
- Forgetting `partial` keyword on CommunityToolkit ViewModel class — source gen fails silently
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!