Avalonia Cheat Sheet
Overview
Avalonia is an open-source, cross-platform UI framework for .NET that enables developers to build desktop, mobile, and web applications from a single C# and XAML codebase. Often described as the WPF of cross-platform, Avalonia provides a familiar XAML-based development experience for .NET developers while supporting Windows, macOS, Linux, iOS, Android, and WebAssembly. It uses its own rendering engine built on Skia, ensuring pixel-perfect consistency across all platforms.
Avalonia supports the MVVM (Model-View-ViewModel) pattern natively, with powerful data binding, styling, and templating systems. It includes a comprehensive set of built-in controls, supports custom controls, offers CSS-like styling with selectors, and provides responsive layouts. The framework integrates with popular .NET libraries and supports both AOT (ahead-of-time) and JIT compilation. Avalonia has been adopted by JetBrains for some of their tools and is used in production by many companies building cross-platform desktop applications.
Installation
# Prerequisites: .NET 8 SDK or later
dotnet --version
# Install Avalonia templates
dotnet new install Avalonia.Templates
# Create new Avalonia project
dotnet new avalonia.app -o MyAvaloniaApp
cd MyAvaloniaApp
# Create MVVM project (with ReactiveUI or CommunityToolkit)
dotnet new avalonia.mvvm -o MyMvvmApp
# Create cross-platform project (desktop + mobile + browser)
dotnet new avalonia.xplat -o MyCrossPlatApp
# Build and run
dotnet build
dotnet run
# With hot reload
dotnet watch run
# Install Avalonia for Visual Studio / VS Code / Rider
# Rider: built-in support
# VS Code: Install "Avalonia for VSCode" extension
# Visual Studio: Install "Avalonia for Visual Studio" extension
Project Structure
MyAvaloniaApp/
├── MyAvaloniaApp.csproj
├── App.axaml # Application resources/styles
├── App.axaml.cs # Application entry
├── Program.cs # Entry point
├── ViewLocator.cs # View-ViewModel resolver
├── Views/
│ ├── MainWindow.axaml # Main window XAML
│ └── MainWindow.axaml.cs # Code-behind
├── ViewModels/
│ ├── ViewModelBase.cs
│ └── MainWindowViewModel.cs
├── Models/
├── Assets/ # Images, fonts, etc.
└── Styles/
└── Themes.axaml
AXAML Basics
<!-- Views/MainWindow.axaml -->
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
x:Class="MyApp.Views.MainWindow"
Title="My Avalonia App"
Width="800" Height="600"
WindowStartupLocation="CenterScreen">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Command="{Binding OpenCommand}" />
<Separator />
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem>
</Menu>
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Welcome to Avalonia!"
FontSize="24"
FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBox Watermark="Enter your name"
Text="{Binding Name}" />
<Button Content="Say Hello"
Command="{Binding GreetCommand}"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding Greeting}"
FontSize="18"
HorizontalAlignment="Center" />
</StackPanel>
</DockPanel>
</Window>
Common Controls
| Control | Description |
|---|---|
<TextBlock> | Display text |
<TextBox> | Text input field |
<Button> | Clickable button |
<ToggleButton> | Toggle state button |
<CheckBox> | Boolean checkbox |
<RadioButton> | Single selection |
<ComboBox> | Dropdown selection |
<Slider> | Range slider |
<ProgressBar> | Progress indicator |
<Image> | Display image |
<ListBox> | Scrollable list |
<DataGrid> | Tabular data display |
<TreeView> | Hierarchical data |
<TabControl> | Tabbed interface |
<Expander> | Collapsible section |
<Calendar> | Date picker |
<NumericUpDown> | Numeric input |
<AutoCompleteBox> | Auto-complete input |
Layouts
<!-- StackPanel -->
<StackPanel Orientation="Vertical" Spacing="8">
<Button Content="First" />
<Button Content="Second" />
<Button Content="Third" />
</StackPanel>
<!-- Grid -->
<Grid RowDefinitions="Auto,*,50"
ColumnDefinitions="200,*"
Margin="10">
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Text="Header" FontSize="20" />
<ListBox Grid.Row="1" Grid.Column="0" />
<Border Grid.Row="1" Grid.Column="1" Background="#f0f0f0">
<TextBlock Text="Content Area" />
</Border>
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Cancel" />
<Button Content="OK" />
</StackPanel>
</Grid>
<!-- DockPanel -->
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top" />
<StatusBar DockPanel.Dock="Bottom" />
<TreeView DockPanel.Dock="Left" Width="200" />
<Border> <!-- Fills remaining space -->
<TextBlock Text="Main Content" />
</Border>
</DockPanel>
<!-- WrapPanel -->
<WrapPanel Orientation="Horizontal">
<Button Content="Tag 1" Margin="4" />
<Button Content="Tag 2" Margin="4" />
<Button Content="Tag 3" Margin="4" />
</WrapPanel>
<!-- UniformGrid -->
<UniformGrid Rows="3" Columns="3">
<Button Content="1" /><Button Content="2" /><Button Content="3" />
<Button Content="4" /><Button Content="5" /><Button Content="6" />
<Button Content="7" /><Button Content="8" /><Button Content="9" />
</UniformGrid>
Data Binding and MVVM
// ViewModels/MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private string _name = string.Empty;
[ObservableProperty]
private string _greeting = string.Empty;
[ObservableProperty]
private ObservableCollection<TodoItem> _items = new();
[RelayCommand]
private void Greet()
{
Greeting = string.IsNullOrWhiteSpace(Name)
? "Hello, World!"
: $"Hello, {Name}!";
}
[RelayCommand]
private async Task OpenAsync()
{
var files = await TopLevel.StorageProvider.OpenFilePickerAsync(
new FilePickerOpenOptions
{
Title = "Open File",
AllowMultiple = false
});
if (files.Count > 0)
{
// Process file
}
}
[RelayCommand]
private void AddItem()
{
if (!string.IsNullOrWhiteSpace(Name))
{
Items.Add(new TodoItem { Title = Name, IsDone = false });
Name = string.Empty;
}
}
}
<!-- Data binding in AXAML -->
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="8">
<CheckBox IsChecked="{Binding IsDone}" />
<TextBlock Text="{Binding Title}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Value converters -->
<TextBlock Text="{Binding Count, StringFormat='Items: {0}'}" />
<TextBlock IsVisible="{Binding HasItems}" />
<TextBlock Text="{Binding Price, StringFormat='{}{0:C}'}" />
Styling
<!-- CSS-like selectors in Avalonia -->
<Window.Styles>
<!-- Target all buttons -->
<Style Selector="Button">
<Setter Property="Background" Value="#0078D4" />
<Setter Property="Foreground" Value="White" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Padding" Value="12,6" />
</Style>
<!-- Target by class -->
<Style Selector="Button.primary">
<Setter Property="Background" Value="#0078D4" />
</Style>
<Style Selector="Button.danger">
<Setter Property="Background" Value="#D32F2F" />
</Style>
<!-- Hover state -->
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#106EBE" />
</Style>
<!-- Child selector -->
<Style Selector="ListBox > ListBoxItem:selected">
<Setter Property="Background" Value="#E3F2FD" />
</Style>
<!-- Descendant selector -->
<Style Selector="StackPanel TextBlock">
<Setter Property="FontSize" Value="14" />
</Style>
</Window.Styles>
<!-- Apply class -->
<Button Classes="primary" Content="Save" />
<Button Classes="danger" Content="Delete" />
Configuration
// Program.cs
using Avalonia;
public class Program
{
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI(); // Or UseMicrosoftDependencyResolver()
}
// App.axaml.cs
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel()
};
}
base.OnFrameworkInitializationCompleted();
}
}
Advanced Usage
// Platform-specific code
if (OperatingSystem.IsWindows())
// Windows-specific logic
else if (OperatingSystem.IsMacOS())
// macOS-specific logic
else if (OperatingSystem.IsLinux())
// Linux-specific logic
// File dialogs
var topLevel = TopLevel.GetTopLevel(this);
var files = await topLevel.StorageProvider.OpenFilePickerAsync(
new FilePickerOpenOptions
{
Title = "Select File",
AllowMultiple = false,
FileTypeFilter = new[]
{
new FilePickerFileType("Text Files") { Patterns = new[] { "*.txt" } },
new FilePickerFileType("All Files") { Patterns = new[] { "*" } }
}
});
// Clipboard
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
await clipboard.SetTextAsync("Copied text");
var text = await clipboard.GetTextAsync();
// Custom rendering
public class CustomControl : Control
{
public override void Render(DrawingContext context)
{
var pen = new Pen(Brushes.Blue, 2);
context.DrawRectangle(Brushes.LightBlue, pen, new Rect(0, 0, Bounds.Width, Bounds.Height));
context.DrawLine(pen, new Point(0, 0), new Point(Bounds.Width, Bounds.Height));
var text = new FormattedText("Hello", CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Arial"), 16, Brushes.Black);
context.DrawText(text, new Point(10, 10));
}
}
// Animations
var animation = new Animation
{
Duration = TimeSpan.FromSeconds(0.5),
Children =
{
new KeyFrame
{
Cue = new Cue(0),
Setters = { new Setter(OpacityProperty, 0.0) }
},
new KeyFrame
{
Cue = new Cue(1),
Setters = { new Setter(OpacityProperty, 1.0) }
}
}
};
await animation.RunAsync(myControl);
Building and Publishing
# Build for current platform
dotnet build -c Release
# Publish self-contained
dotnet publish -c Release -r win-x64 --self-contained
dotnet publish -c Release -r osx-arm64 --self-contained
dotnet publish -c Release -r linux-x64 --self-contained
# Single file publish
dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true
# Trimmed publish (smaller binary)
dotnet publish -c Release -r win-x64 --self-contained -p:PublishTrimmed=true
# AOT compilation
dotnet publish -c Release -r win-x64 -p:PublishAot=true
Troubleshooting
| Issue | Solution |
|---|---|
| AXAML designer not working | Install Avalonia extension for your IDE; rebuild project |
| Binding not updating | Ensure ViewModel implements INotifyPropertyChanged or uses [ObservableProperty] |
| Control not rendering | Check layout properties (Width/Height/HorizontalAlignment) |
| Linux display issues | Install Skia dependencies: libskia-sharp or libX11 |
| Font rendering different | Use WithInterFont() for consistent cross-platform fonts |
| Style not applying | Check selector syntax; Avalonia uses CSS-like selectors, not WPF style |
| Hot reload not working | Use dotnet watch run; check file save triggers |
| macOS app not signing | Use dotnet publish with proper entitlements and code signing |
| Memory leak | Dispose subscriptions; use WeakReference for event handlers |
| Build slow | Enable incremental build; consider trimming in release |