Skip to main content

Styling and Theming

Styling and theming are essential aspects of creating visually appealing and consistent user interfaces. Avalonia UI provides a powerful styling system that allows you to customize the appearance of controls and create cohesive themes for your applications.

Styles and Resources​

Avalonia UI's styling system is inspired by CSS and allows you to define styles that target specific controls or control types.

Resources​

Resources in Avalonia UI are objects that can be reused throughout your application. They are typically defined in resource dictionaries:

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.MainWindow">

<Window.Resources>
<SolidColorBrush x:Key="PrimaryBrush" Color="#1E88E5" />
<SolidColorBrush x:Key="AccentBrush" Color="#FF4081" />
<SolidColorBrush x:Key="BackgroundBrush" Color="#F5F5F5" />
<SolidColorBrush x:Key="ForegroundBrush" Color="#212121" />
</Window.Resources>

<Button Background="{StaticResource PrimaryBrush}"
Foreground="White"
Content="Styled Button" />

</Window>

Resources can be defined at different levels:

  • Control level: Available only to the control and its children
  • Window level: Available to the entire window
  • Application level: Available to the entire application

Application-level resources are defined in App.axaml:

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App">

<Application.Resources>
<SolidColorBrush x:Key="PrimaryBrush" Color="#1E88E5" />
<SolidColorBrush x:Key="AccentBrush" Color="#FF4081" />
</Application.Resources>

</Application>

Resource Dictionaries​

For better organization, you can define resources in separate resource dictionary files:

<!-- Themes/Colors.axaml -->
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Color x:Key="PrimaryColor">#1E88E5</Color>
<Color x:Key="AccentColor">#FF4081</Color>
<Color x:Key="BackgroundColor">#F5F5F5</Color>
<Color x:Key="ForegroundColor">#212121</Color>

<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}" />
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColor}" />
<SolidColorBrush x:Key="ForegroundBrush" Color="{StaticResource ForegroundColor}" />

</ResourceDictionary>

You can merge multiple resource dictionaries:

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App">

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="/Themes/Colors.axaml" />
<ResourceInclude Source="/Themes/Fonts.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

</Application>

Styles​

Styles in Avalonia UI allow you to set properties on controls based on selectors. They are similar to CSS styles:

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.MainWindow">

<Window.Styles>
<!-- Style targeting all Buttons -->
<Style Selector="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="CornerRadius" Value="4" />
</Style>

<!-- Style targeting Buttons with class 'accent' -->
<Style Selector="Button.accent">
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
</Style>

<!-- Style targeting TextBlocks inside StackPanels -->
<Style Selector="StackPanel > TextBlock">
<Setter Property="Margin" Value="0,5" />
</Style>
</Window.Styles>

<StackPanel Margin="20">
<TextBlock Text="Regular Button:" />
<Button Content="Regular Button" />

<TextBlock Text="Accent Button:" />
<Button Content="Accent Button" Classes="accent" />
</StackPanel>

</Window>

Style Selectors​

Avalonia UI provides a powerful selector system for targeting controls:

  • Type selector: Button targets all buttons
  • Name selector: #myButton targets the control with name "myButton"
  • Class selector: .accent targets controls with the "accent" class
  • Descendant selector: StackPanel TextBlock targets TextBlocks inside StackPanels
  • Child selector: StackPanel > TextBlock targets TextBlocks that are direct children of StackPanels
  • Property selector: Button[IsDefault=true] targets Buttons with IsDefault property set to true
  • Pseudo-class selector: Button:pointerover targets Buttons in the pointer-over state

You can combine selectors for more specific targeting:

<Style Selector="Button.accent:pointerover">
<Setter Property="Background" Value="{StaticResource AccentHoverBrush}" />
</Style>

Pseudo-classes​

Pseudo-classes target controls in specific states:

  • :pointerover - When the pointer is over the control
  • :pressed - When the control is pressed
  • :disabled - When the control is disabled
  • :focus - When the control has focus
  • :selected - When the control is selected (for selectable controls)
  • :checked - When the control is checked (for checkable controls)

Example of using pseudo-classes:

<Style Selector="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
</Style>

<Style Selector="Button:pointerover">
<Setter Property="Background" Value="{StaticResource PrimaryHoverBrush}" />
</Style>

<Style Selector="Button:pressed">
<Setter Property="Background" Value="{StaticResource PrimaryPressedBrush}" />
</Style>

<Style Selector="Button:disabled">
<Setter Property="Opacity" Value="0.5" />
</Style>

Style Classes​

You can apply style classes to controls to target them with class selectors:

<Button Content="Accent Button" Classes="accent" />

You can add or remove classes programmatically:

// Add a class
myButton.Classes.Add("accent");

// Remove a class
myButton.Classes.Remove("accent");

// Toggle a class
myButton.Classes.Toggle("accent");

// Check if a control has a class
bool hasAccentClass = myButton.Classes.Contains("accent");

Themes and Skins​

Themes in Avalonia UI are collections of styles and resources that define the overall look and feel of your application.

Built-in Themes​

Avalonia UI comes with two built-in themes:

  1. Fluent Theme: Based on Microsoft's Fluent Design System
  2. Simple Theme: A minimal theme with basic styling

To use the Fluent theme:

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App">

<Application.Styles>
<FluentTheme />
</Application.Styles>

</Application>

You can specify the theme variant (light or dark):

<FluentTheme Mode="Light" />

Or:

<FluentTheme Mode="Dark" />

Creating Custom Themes​

You can create custom themes by defining styles and resources that override the default appearance of controls:

<!-- Themes/CustomTheme.axaml -->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- Include a base theme -->
<FluentTheme />

<!-- Override styles -->
<Style Selector="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Padding" Value="15,8" />
<Setter Property="CornerRadius" Value="20" />
</Style>

<Style Selector="TextBox">
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}" />
<Setter Property="CornerRadius" Value="4" />
</Style>

<!-- More style overrides... -->

</Styles>

Apply the custom theme in your application:

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App">

<Application.Styles>
<StyleInclude Source="/Themes/CustomTheme.axaml" />
</Application.Styles>

</Application>

Theme Switching​

You can implement theme switching to allow users to change the application theme at runtime:

public void SwitchTheme(bool isDark)
{
var app = Application.Current;
if (app is null) return;

// Remove existing theme
var existingTheme = app.Styles.OfType<FluentTheme>().FirstOrDefault();
if (existingTheme != null)
app.Styles.Remove(existingTheme);

// Add new theme
var newTheme = new FluentTheme
{
Mode = isDark ? FluentThemeMode.Dark : FluentThemeMode.Light
};
app.Styles.Add(newTheme);
}

Custom Controls​

Sometimes, you need to create custom controls to implement specialized functionality or appearance.

Creating a Custom Control​

To create a custom control, you typically inherit from an existing control class:

public class RatingControl : Control
{
public static readonly StyledProperty<int> ValueProperty =
AvaloniaProperty.Register<RatingControl, int>(nameof(Value), defaultValue: 0);

public static readonly StyledProperty<int> MaximumProperty =
AvaloniaProperty.Register<RatingControl, int>(nameof(Maximum), defaultValue: 5);

public int Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, Math.Clamp(value, 0, Maximum));
}

public int Maximum
{
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, Math.Max(1, value));
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == ValueProperty || change.Property == MaximumProperty)
{
InvalidateVisual();
}
}

public override void Render(DrawingContext context)
{
var size = Bounds.Size;
var starWidth = size.Width / Maximum;
var starHeight = size.Height;

for (int i = 0; i < Maximum; i++)
{
var starRect = new Rect(i * starWidth, 0, starWidth, starHeight);
var brush = i < Value ? Brushes.Gold : Brushes.Gray;

// Draw a simple star (or any other shape)
context.DrawRectangle(brush, null, starRect.Deflate(2));
}
}

protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);

var point = e.GetPosition(this);
var starWidth = Bounds.Width / Maximum;
var newValue = (int)(point.X / starWidth) + 1;

Value = newValue;
}
}

Creating a Templated Control​

For more complex controls, you can create a templated control with a customizable appearance:

public class RatingControl : TemplatedControl
{
public static readonly StyledProperty<int> ValueProperty =
AvaloniaProperty.Register<RatingControl, int>(nameof(Value), defaultValue: 0);

public static readonly StyledProperty<int> MaximumProperty =
AvaloniaProperty.Register<RatingControl, int>(nameof(Maximum), defaultValue: 5);

public int Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, Math.Clamp(value, 0, Maximum));
}

public int Maximum
{
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, Math.Max(1, value));
}

protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

// Find elements in the template
var starsPanel = e.NameScope.Find<Panel>("PART_StarsPanel");
if (starsPanel != null)
{
starsPanel.Children.Clear();

for (int i = 0; i < Maximum; i++)
{
var star = new Button
{
Content = "★",
Tag = i + 1,
Classes = { i < Value ? "selected" : "" }
};

star.Click += (sender, args) =>
{
if (sender is Button button && button.Tag is int newValue)
{
Value = newValue;
}
};

starsPanel.Children.Add(star);
}
}
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == ValueProperty)
{
var starsPanel = this.FindControl<Panel>("PART_StarsPanel");
if (starsPanel != null)
{
for (int i = 0; i < starsPanel.Children.Count; i++)
{
if (starsPanel.Children[i] is Button star)
{
if (i < Value)
star.Classes.Add("selected");
else
star.Classes.Remove("selected");
}
}
}
}
else if (change.Property == MaximumProperty)
{
// Re-apply the template to update the number of stars
InvalidateTemplate();
}
}
}

Define the control template in a style:

<Style Selector="RatingControl">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<StackPanel Name="PART_StarsPanel" Orientation="Horizontal" />
</Border>
</ControlTemplate>
</Setter>
</Style>

<Style Selector="RatingControl Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="2" />
</Style>

<Style Selector="RatingControl Button.selected">
<Setter Property="Foreground" Value="Gold" />
</Style>

Visual States​

Visual states allow you to define different appearances for controls based on their state.

Defining Visual States​

Visual states are defined using the VisualState and VisualStateManager classes:

<Style Selector="Button">
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>

<ControlTemplate.Transitions>
<Transitions>
<BrushTransition Property="Border.Background" Duration="0:0:0.2" />
</Transitions>
</ControlTemplate.Transitions>
</ControlTemplate>
</Setter>
</Style>

<Style Selector="Button:pointerover /template/ Border#border">
<Setter Property="Background" Value="{StaticResource PrimaryHoverBrush}" />
</Style>

<Style Selector="Button:pressed /template/ Border#border">
<Setter Property="Background" Value="{StaticResource PrimaryPressedBrush}" />
</Style>

<Style Selector="Button:disabled /template/ Border#border">
<Setter Property="Opacity" Value="0.5" />
</Style>

Transitions​

Transitions define how properties animate when they change:

<ControlTemplate.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
<BrushTransition Property="Background" Duration="0:0:0.2" />
<ThicknessTransition Property="Margin" Duration="0:0:0.2" />
</Transitions>
</ControlTemplate.Transitions>

Custom Visual States​

You can define custom visual states for your controls:

public class CustomControl : TemplatedControl
{
public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<CustomControl, bool>(nameof(IsExpanded));

public bool IsExpanded
{
get => GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == IsExpandedProperty)
{
PseudoClasses.Set(":expanded", IsExpanded);
}
}
}

Then you can target the custom state in styles:

<Style Selector="CustomControl:expanded">
<Setter Property="Height" Value="200" />
</Style>

Example: Creating a Custom Theme​

Let's put everything together to create a custom theme for an application:

<!-- Themes/CustomTheme.axaml -->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- Include a base theme -->
<FluentTheme />

<!-- Define colors -->
<Style.Resources>
<Color x:Key="PrimaryColor">#3F51B5</Color>
<Color x:Key="PrimaryLightColor">#7986CB</Color>
<Color x:Key="PrimaryDarkColor">#303F9F</Color>
<Color x:Key="AccentColor">#FF4081</Color>
<Color x:Key="AccentLightColor">#FF80AB</Color>
<Color x:Key="AccentDarkColor">#F50057</Color>
<Color x:Key="BackgroundColor">#FAFAFA</Color>
<Color x:Key="ForegroundColor">#212121</Color>
<Color x:Key="BorderColor">#BDBDBD</Color>

<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}" />
<SolidColorBrush x:Key="PrimaryLightBrush" Color="{StaticResource PrimaryLightColor}" />
<SolidColorBrush x:Key="PrimaryDarkBrush" Color="{StaticResource PrimaryDarkColor}" />
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
<SolidColorBrush x:Key="AccentLightBrush" Color="{StaticResource AccentLightColor}" />
<SolidColorBrush x:Key="AccentDarkBrush" Color="{StaticResource AccentDarkColor}" />
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColor}" />
<SolidColorBrush x:Key="ForegroundBrush" Color="{StaticResource ForegroundColor}" />
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}" />
</Style.Resources>

<!-- Window styles -->
<Style Selector="Window">
<Setter Property="Background" Value="{StaticResource BackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
</Style>

<!-- Button styles -->
<Style Selector="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="15,8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>

<Style Selector="Button:pointerover">
<Setter Property="Background" Value="{StaticResource PrimaryLightBrush}" />
</Style>

<Style Selector="Button:pressed">
<Setter Property="Background" Value="{StaticResource PrimaryDarkBrush}" />
</Style>

<Style Selector="Button:disabled">
<Setter Property="Opacity" Value="0.6" />
</Style>

<Style Selector="Button.accent">
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
</Style>

<Style Selector="Button.accent:pointerover">
<Setter Property="Background" Value="{StaticResource AccentLightBrush}" />
</Style>

<Style Selector="Button.accent:pressed">
<Setter Property="Background" Value="{StaticResource AccentDarkBrush}" />
</Style>

<!-- TextBox styles -->
<Style Selector="TextBox">
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="Background" Value="White" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Padding" Value="8,6" />
</Style>

<Style Selector="TextBox:focus">
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}" />
</Style>

<!-- CheckBox styles -->
<Style Selector="CheckBox">
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
</Style>

<Style Selector="CheckBox:checked /template/ Border#PART_Border">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}" />
</Style>

<!-- ComboBox styles -->
<Style Selector="ComboBox">
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="Background" Value="White" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Padding" Value="8,6" />
</Style>

<Style Selector="ComboBox:focus">
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}" />
</Style>

<!-- ListBox styles -->
<Style Selector="ListBox">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
</Style>

<Style Selector="ListBoxItem:selected">
<Setter Property="Background" Value="{StaticResource PrimaryLightBrush}" />
<Setter Property="Foreground" Value="White" />
</Style>

<!-- TabControl styles -->
<Style Selector="TabControl">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
</Style>

<Style Selector="TabItem">
<Setter Property="Padding" Value="15,8" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
</Style>

<Style Selector="TabItem:selected">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
</Style>

<!-- More control styles... -->

</Styles>

Apply the custom theme in your application:

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App">

<Application.Styles>
<StyleInclude Source="/Themes/CustomTheme.axaml" />
</Application.Styles>

</Application>

This custom theme:

  • Defines a color palette with primary, accent, and neutral colors
  • Applies consistent styling to common controls
  • Includes hover, pressed, and disabled states
  • Provides an accent variant for buttons
  • Maintains a cohesive look and feel across the application

In the next section, we'll explore handling events and commands in Avalonia UI.