Avalonia

WPF-style cross-platform .NET desktop framework. Supports XAML, data binding, and MVVM patterns, running on Windows, macOS, and Linux. Low learning cost for WPF developers with modern UI development capabilities.

desktopC#XAMLcross-platform.NETopen-source

GitHub Overview

AvaloniaUI/Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology

Stars28,163
Watchers470
Forks2,434
Created:December 5, 2013
Language:C#
License:MIT License

Topics

androidapp-frameworkavaloniaavaloniauibrowserc-sharpcross-platformcsharpdesktopdotnetguiioslinuxmacosmobilemvvmwasmwindowsxamarinxaml

Star History

AvaloniaUI/Avalonia Star History
Data as of: 7/15/2025, 11:10 PM

Desktop Framework

Avalonia

Overview

Avalonia is a cross-platform UI framework that enables development of desktop, embedded, mobile, and WebAssembly applications using C# and XAML. With WPF-like XAML syntax, you can develop applications that run on Windows, macOS, Linux, iOS, Android, and WebAssembly from a single codebase. It's positioned as the most popular UI client technology for .NET.

Details

Avalonia is a modern UI framework that inherits the design philosophy of WPF. It provides familiar features for WPF developers including declarative UI definition with XAML, MVVM pattern support, data binding, command patterns, and more.

Key features include true cross-platform support (Windows, macOS, Linux, mobile, Web), WPF-compatible XAML, powerful data binding, custom control creation, rich layout system, theming and styling, and Hot Reload support.

It's adopted by many commercial and open-source projects including Telegram Desktop, Wasabi Wallet, and GameBuilder. Integration with ReactiveUI also enables reactive programming patterns.

Features

  • True Cross-Platform: Single codebase for all platforms
  • WPF-like API: Low learning curve for WPF developers
  • High Performance: Fast rendering with native rendering
  • Rich Controls: Standard controls with high customizability
  • Open Source: Free to use under MIT license
  • Active Community: Frequent updates and support

Architecture

  • MVVM Support: Built-in Model-View-ViewModel pattern support
  • Data Binding: Two-way data binding and property notification
  • Styling System: CSS-like styling with selectors and triggers
  • Control Templates: Complete control customization capabilities
  • Layout System: Flexible layout with panels and controls
  • Resource Management: Efficient resource handling and disposal

Pros and Cons

Pros

  • True cross-platform development with single codebase
  • WPF-similar API reduces learning curve for .NET developers
  • High performance with native rendering on each platform
  • Rich set of controls and high customizability
  • Open source with MIT license - free for commercial use
  • Active community with frequent updates and improvements
  • Strong MVVM and data binding support
  • Hot Reload for rapid development cycles
  • Integration with ReactiveUI for reactive programming
  • Growing ecosystem with NuGet package support

Cons

  • Not fully WPF compatible - some APIs and features differ
  • Smaller ecosystem compared to WPF with fewer third-party libraries
  • Self-contained application packages can be large
  • Less learning resources compared to WPF - documentation and tutorials
  • Platform-specific feature limitations compared to native development
  • Still maturing - less proven track record than WPF
  • Advanced scenarios may require platform-specific code
  • Designer tools not as mature as Visual Studio WPF designer

Reference Pages

Code Examples

Hello World

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="HelloAvalonia.MainWindow"
        Title="Hello Avalonia" Width="400" Height="300">
    <StackPanel Margin="20">
        <TextBlock Text="Hello, Avalonia World!" 
                   FontSize="24" 
                   FontWeight="Bold"
                   HorizontalAlignment="Center"
                   Margin="0,20"/>
        <Button Content="Click Me"
                Name="HelloButton"
                HorizontalAlignment="Center"
                Padding="20,10"
                Click="HelloButton_Click"/>
    </StackPanel>
</Window>
using Avalonia.Controls;
using Avalonia.Interactivity;

namespace HelloAvalonia
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void HelloButton_Click(object? sender, RoutedEventArgs e)
        {
            var button = sender as Button;
            if (button != null)
            {
                button.Content = "Thank you!";
            }
        }
    }
}

Data Binding and MVVM Pattern

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="AvaloniaApp.MainWindow"
        Title="Data Binding Example" Width="450" Height="350">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBox Grid.Row="0" Text="{Binding UserName, Mode=TwoWay}"
                 Watermark="Enter your name..." Margin="0,10"/>
        
        <TextBlock Grid.Row="1" Text="{Binding Greeting}" 
                   FontSize="18" FontWeight="Bold" Margin="0,10"/>
        
        <Button Grid.Row="2" Content="Update Greeting" 
                Command="{Binding UpdateGreetingCommand}"
                HorizontalAlignment="Center" Margin="0,10"/>
        
        <ListBox Grid.Row="3" ItemsSource="{Binding Messages}" Margin="0,10">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}" Margin="5"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Avalonia.Controls;

namespace AvaloniaApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private string _userName = "";
        private string _greeting = "Hello!";

        public string UserName
        {
            get => _userName;
            set
            {
                _userName = value;
                OnPropertyChanged();
                UpdateGreeting();
            }
        }

        public string Greeting
        {
            get => _greeting;
            set
            {
                _greeting = value;
                OnPropertyChanged();
            }
        }

        public ObservableCollection<string> Messages { get; } = new();

        public ICommand UpdateGreetingCommand => new RelayCommand(UpdateGreeting);

        private void UpdateGreeting()
        {
            var message = string.IsNullOrEmpty(UserName) 
                ? "Hello!" 
                : $"Hello, {UserName}!";
            
            Greeting = message;
            Messages.Add($"{DateTime.Now:HH:mm:ss} - {message}");
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    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 bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;

        public void Execute(object? parameter) => _execute();

        public event EventHandler? CanExecuteChanged;
    }
}

Layout and Styling

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="AvaloniaApp.LayoutWindow"
        Title="Layout Example" Width="600" Height="400">
    
    <Window.Styles>
        <Style Selector="Button.primary">
            <Setter Property="Background" Value="#007ACC"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Padding" Value="12,6"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="BorderRadius" Value="4"/>
        </Style>
        
        <Style Selector="Button.primary:pointerover">
            <Setter Property="Background" Value="#005A9E"/>
        </Style>

        <Style Selector="TextBlock.header">
            <Setter Property="FontSize" Value="20"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Margin" Value="0,0,0,10"/>
        </Style>
    </Window.Styles>

    <DockPanel Margin="20">
        <!-- Header -->
        <TextBlock DockPanel.Dock="Top" Classes="header"
                   Text="Avalonia Layout Example" HorizontalAlignment="Center"/>
        
        <!-- Sidebar -->
        <Border DockPanel.Dock="Left" Width="200" Background="#F5F5F5" 
                Padding="10" Margin="0,0,10,0">
            <StackPanel>
                <TextBlock Text="Side Menu" FontWeight="Bold" Margin="0,0,0,10"/>
                <Button Classes="primary" Content="Home" HorizontalAlignment="Stretch"/>
                <Button Classes="primary" Content="Settings" HorizontalAlignment="Stretch"/>
                <Button Classes="primary" Content="Help" HorizontalAlignment="Stretch"/>
            </StackPanel>
        </Border>
        
        <!-- Status bar -->
        <Border DockPanel.Dock="Bottom" Height="30" Background="#E0E0E0" Padding="10,5">
            <TextBlock Text="Ready" VerticalAlignment="Center"/>
        </Border>
        
        <!-- Main content -->
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <TabControl Grid.Row="0">
                <TabItem Header="Form">
                    <ScrollViewer Padding="20">
                        <StackPanel Spacing="15">
                            <TextBox Watermark="Name" Width="300" HorizontalAlignment="Left"/>
                            <TextBox Watermark="Email Address" Width="300" HorizontalAlignment="Left"/>
                            <ComboBox Width="300" HorizontalAlignment="Left">
                                <ComboBoxItem Content="Option 1"/>
                                <ComboBoxItem Content="Option 2"/>
                                <ComboBoxItem Content="Option 3"/>
                            </ComboBox>
                            <CheckBox Content="Subscribe to newsletter"/>
                            <StackPanel Orientation="Horizontal" Spacing="10">
                                <Button Classes="primary" Content="Submit"/>
                                <Button Content="Reset"/>
                            </StackPanel>
                        </StackPanel>
                    </ScrollViewer>
                </TabItem>
                
                <TabItem Header="Data">
                    <DataGrid ItemsSource="{Binding SampleData}" 
                              AutoGenerateColumns="True"
                              GridLinesVisibility="All"
                              IsReadOnly="True"/>
                </TabItem>
            </TabControl>
            
            <GridSplitter Grid.Row="0" Height="5" Background="#CCC" 
                         HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
            
            <TextBox Grid.Row="1" Height="100" 
                     Watermark="Log output area..." 
                     TextWrapping="Wrap" AcceptsReturn="True"/>
        </Grid>
    </DockPanel>
</Window>

Custom Controls

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;

namespace AvaloniaApp.Controls
{
    public class CustomButton : Button
    {
        public static readonly StyledProperty<IBrush> HoverBackgroundProperty =
            AvaloniaProperty.Register<CustomButton, IBrush>(nameof(HoverBackground));

        public static readonly StyledProperty<double> CornerRadiusProperty =
            AvaloniaProperty.Register<CustomButton, double>(nameof(CornerRadius));

        static CustomButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(CustomButton), 
                new FuncStyleKey<CustomButton>(x => typeof(CustomButton)));
        }

        public IBrush HoverBackground
        {
            get => GetValue(HoverBackgroundProperty);
            set => SetValue(HoverBackgroundProperty, value);
        }

        public double CornerRadius
        {
            get => GetValue(CornerRadiusProperty);
            set => SetValue(CornerRadiusProperty, value);
        }
    }
}
<!-- Styles.axaml -->
<Styles xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="using:AvaloniaApp.Controls">
    
    <Style Selector="controls|CustomButton">
        <Setter Property="Template">
            <ControlTemplate>
                <Border Name="PART_Border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="{TemplateBinding CornerRadius}"
                        Padding="{TemplateBinding Padding}">
                    <ContentPresenter Content="{TemplateBinding Content}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>
            </ControlTemplate>
        </Setter>
    </Style>
    
    <Style Selector="controls|CustomButton:pointerover /template/ Border#PART_Border">
        <Setter Property="Background" Value="{Binding $parent[controls:CustomButton].HoverBackground}"/>
    </Style>
    
    <Style Selector="controls|CustomButton:pressed /template/ Border#PART_Border">
        <Setter Property="RenderTransform" Value="scale(0.98)"/>
    </Style>
</Styles>

File Operations and Dialogs

using Avalonia.Controls;
using Avalonia.Platform.Storage;
using System.IO;

namespace AvaloniaApp
{
    public partial class FileOperationWindow : Window
    {
        public FileOperationWindow()
        {
            InitializeComponent();
        }

        private async void OpenFileButton_Click(object? sender, RoutedEventArgs e)
        {
            var topLevel = GetTopLevel(this);
            if (topLevel == null) return;

            var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
            {
                Title = "Open File",
                AllowMultiple = false,
                FileTypeFilter = new[]
                {
                    new FilePickerFileType("Text Files")
                    {
                        Patterns = new[] { "*.txt", "*.md" }
                    },
                    new FilePickerFileType("All Files")
                    {
                        Patterns = new[] { "*" }
                    }
                }
            });

            if (files.Count > 0)
            {
                var file = files[0];
                using var stream = await file.OpenReadAsync();
                using var reader = new StreamReader(stream);
                var content = await reader.ReadToEndAsync();
                
                // Display content in TextBlock
                if (this.FindControl<TextBlock>("ContentTextBlock") is TextBlock textBlock)
                {
                    textBlock.Text = content;
                }
            }
        }

        private async void SaveFileButton_Click(object? sender, RoutedEventArgs e)
        {
            var topLevel = GetTopLevel(this);
            if (topLevel == null) return;

            var file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
            {
                Title = "Save File",
                FileTypeChoices = new[]
                {
                    new FilePickerFileType("Text Files")
                    {
                        Patterns = new[] { "*.txt" }
                    }
                }
            });

            if (file != null)
            {
                var content = "File saved from Avalonia\n" + 
                             $"Save time: {DateTime.Now:yyyy/MM/dd HH:mm:ss}";
                
                using var stream = await file.OpenWriteAsync();
                using var writer = new StreamWriter(stream);
                await writer.WriteAsync(content);
            }
        }

        private async void ShowMessageButton_Click(object? sender, RoutedEventArgs e)
        {
            var result = await ShowDialog("Confirmation", "Do you want to execute the operation?", "Yes", "No");
            
            if (result)
            {
                await ShowDialog("Result", "Operation executed successfully.", "OK");
            }
        }

        private async Task<bool> ShowDialog(string title, string message, string primaryButton, string? secondaryButton = null)
        {
            var dialog = new Window
            {
                Title = title,
                Width = 400,
                Height = 200,
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                CanResize = false
            };

            var result = false;
            var panel = new StackPanel { Margin = new Thickness(20), Spacing = 20 };
            
            panel.Children.Add(new TextBlock 
            { 
                Text = message, 
                TextWrapping = TextWrapping.Wrap,
                HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center
            });

            var buttonPanel = new StackPanel 
            { 
                Orientation = Avalonia.Layout.Orientation.Horizontal,
                HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
                Spacing = 10
            };

            var primaryBtn = new Button { Content = primaryButton, Padding = new Thickness(20, 5) };
            primaryBtn.Click += (s, e) => { result = true; dialog.Close(); };
            buttonPanel.Children.Add(primaryBtn);

            if (secondaryButton != null)
            {
                var secondaryBtn = new Button { Content = secondaryButton, Padding = new Thickness(20, 5) };
                secondaryBtn.Click += (s, e) => { result = false; dialog.Close(); };
                buttonPanel.Children.Add(secondaryBtn);
            }

            panel.Children.Add(buttonPanel);
            dialog.Content = panel;

            await dialog.ShowDialog(this);
            return result;
        }
    }
}

Animations and Transitions

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="AvaloniaApp.AnimationWindow"
        Title="Animation Example" Width="500" Height="400">
    
    <Window.Styles>
        <Style Selector="Button.animated">
            <Setter Property="Background" Value="#4CAF50"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Padding" Value="20,10"/>
            <Setter Property="Margin" Value="10"/>
            <Setter Property="Transitions">
                <Transitions>
                    <BrushTransition Property="Background" Duration="0:0:0.3"/>
                    <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2"/>
                </Transitions>
            </Setter>
        </Style>
        
        <Style Selector="Button.animated:pointerover">
            <Setter Property="Background" Value="#45a049"/>
            <Setter Property="RenderTransform" Value="scale(1.05)"/>
        </Style>
        
        <Style Selector="Button.animated:pressed">
            <Setter Property="RenderTransform" Value="scale(0.95)"/>
        </Style>
        
        <Style Selector="Border.slide-in">
            <Setter Property="Opacity" Value="0"/>
            <Setter Property="RenderTransform" Value="translateX(-50)"/>
        </Style>
        
        <Style Selector="Border.slide-in.visible">
            <Setter Property="Opacity" Value="1"/>
            <Setter Property="RenderTransform" Value="translateX(0)"/>
            <Setter Property="Transitions">
                <Transitions>
                    <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
                    <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5"/>
                </Transitions>
            </Setter>
        </Style>
    </Window.Styles>

    <StackPanel Margin="20" Spacing="20">
        <TextBlock Text="Avalonia Animation Example" 
                   FontSize="24" FontWeight="Bold" 
                   HorizontalAlignment="Center"/>
        
        <Button Classes="animated" Content="Hover Animation" 
                HorizontalAlignment="Center"/>
        
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
            <Button Content="Show Slide In" Click="ShowSlideIn_Click"/>
            <Button Content="Fade Out" Click="FadeOut_Click"/>
            <Button Content="Rotate Animation" Click="Rotate_Click"/>
        </StackPanel>
        
        <Border Name="AnimatedBorder" Classes="slide-in"
                Width="300" Height="100" 
                Background="#2196F3" CornerRadius="10"
                HorizontalAlignment="Center">
            <TextBlock Text="Animation Target" 
                       Foreground="White" FontSize="16"
                       HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
        
        <Rectangle Name="RotatingRectangle" 
                   Width="50" Height="50" 
                   Fill="#FF5722" 
                   HorizontalAlignment="Center"
                   RenderTransformOrigin="0.5,0.5"/>
    </StackPanel>
</Window>
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;

namespace AvaloniaApp
{
    public partial class AnimationWindow : Window
    {
        public AnimationWindow()
        {
            InitializeComponent();
        }

        private void ShowSlideIn_Click(object? sender, RoutedEventArgs e)
        {
            var border = this.FindControl<Border>("AnimatedBorder");
            if (border != null)
            {
                if (border.Classes.Contains("visible"))
                {
                    border.Classes.Remove("visible");
                }
                else
                {
                    border.Classes.Add("visible");
                }
            }
        }

        private async void FadeOut_Click(object? sender, RoutedEventArgs e)
        {
            var border = this.FindControl<Border>("AnimatedBorder");
            if (border != null)
            {
                var animation = new Animation
                {
                    Duration = TimeSpan.FromSeconds(1),
                    Children =
                    {
                        new KeyFrame
                        {
                            Cue = new Cue(0d),
                            Setters = { new Setter(OpacityProperty, 1d) }
                        },
                        new KeyFrame
                        {
                            Cue = new Cue(1d),
                            Setters = { new Setter(OpacityProperty, 0d) }
                        }
                    }
                };
                
                await animation.RunAsync(border);
                
                // Fade back in
                var fadeInAnimation = new Animation
                {
                    Duration = TimeSpan.FromSeconds(0.5),
                    Children =
                    {
                        new KeyFrame
                        {
                            Cue = new Cue(0d),
                            Setters = { new Setter(OpacityProperty, 0d) }
                        },
                        new KeyFrame
                        {
                            Cue = new Cue(1d),
                            Setters = { new Setter(OpacityProperty, 1d) }
                        }
                    }
                };
                
                await fadeInAnimation.RunAsync(border);
            }
        }

        private async void Rotate_Click(object? sender, RoutedEventArgs e)
        {
            var rectangle = this.FindControl<Rectangle>("RotatingRectangle");
            if (rectangle != null)
            {
                var animation = new Animation
                {
                    Duration = TimeSpan.FromSeconds(2),
                    IterationCount = IterationCount.Infinite,
                    Children =
                    {
                        new KeyFrame
                        {
                            Cue = new Cue(0d),
                            Setters = 
                            { 
                                new Setter(RenderTransformProperty, new RotateTransform(0)) 
                            }
                        },
                        new KeyFrame
                        {
                            Cue = new Cue(1d),
                            Setters = 
                            { 
                                new Setter(RenderTransformProperty, new RotateTransform(360)) 
                            }
                        }
                    }
                };
                
                await animation.RunAsync(rectangle);
            }
        }
    }
}