Getting started
Avalloy is a set of NuGet packages for Avalonia desktop apps on .NET 10. Two of them make an app: Avalloy.Core is the app shell, and Avalloy.Themes is the theme engine and the neutral templates it ships. The other three are opt-in.
dotnet add package Avalloy.Core
dotnet add package Avalloy.ThemesEverything restores from nuget.org. There is no private feed and no credential.
The app class
Derive from AvalloyApp. It owns the cross-cutting bootstrap: the DI container, logging with a console, debug, rolling-file and in-app buffer provider, the command registry and keyboard dispatcher, the main window, and the theme lifecycle. You override the handful of hooks that are yours.
public partial class App : AvalloyApp
{
private PreferencesService<MyPreferences>? _prefs;
private static readonly IReadOnlyList<ThemeDescriptor> ThemeCatalog =
[
new("Light", "avares://Avalloy.Themes/Themes/Template.Light.axaml", Dark: false),
new("Dark", "avares://Avalloy.Themes/Themes/Template.Dark.axaml", Dark: true),
];
public override void Initialize() => AvaloniaXamlLoader.Load(this);
protected override void ConfigureServices(IServiceCollection services)
{
var dir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyApp");
_prefs = new PreferencesService<MyPreferences>(dir);
_prefs.Initialize(); // create the dir, load, hook debounced auto-save
services.AddSingleton(_prefs);
}
protected override Window CreateMainWindow() => new MainWindow();
protected override IThemeController? ResolveThemeController() =>
new NativeThemeController(this, ThemeCatalog, defaultLightId: "Light", defaultDarkId: "Dark");
protected override IAppearanceModel? GetThemeAppearance() => _prefs!.Current.Appearance;
}That is a themed app that persists its preferences and follows the OS light/dark switch. ResolveThemeController is the theme seam: answer it with Avalloy's native controller, with Core's preset cascade, or with an engine of your own.
App.axaml
The theme engine needs Fluent underneath, the type ramp and control styles from Themes, and the theme-independent tokens merged in. The per-variant palette is what the controller swaps at runtime.
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp.App"
RequestedThemeVariant="Dark">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalloy.Themes/Themes/Type.axaml" />
<StyleInclude Source="avares://Avalloy.Themes/Themes/ControlStyles.axaml" />
</Application.Styles>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Avalloy.Themes/Themes/Tokens.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>The preferences model
Preferences are a plain observable object. Embed AppearancePrefs and the theme engine reads it through IAppearanceModel; implement IPreferencesModel so the service can watch for mutations without knowing your section layout.
public sealed partial class MyPreferences : ObservableObject, IPreferencesModel
{
[ObservableProperty]
private AppearancePrefs _appearance = new()
{
ThemePresetLight = "Light",
ThemePresetDark = "Dark",
ThemeVariant = ThemeVariantPreference.System,
};
public void RegisterMutationHandler(PropertyChangedEventHandler handler)
{
PropertyChanged += handler;
Appearance.PropertyChanged += handler;
}
public void UnregisterMutationHandler(PropertyChangedEventHandler handler)
{
PropertyChanged -= handler;
Appearance.PropertyChanged -= handler;
}
}The file is preferences.json in the directory you gave the service. Edit it by hand and the app reloads; see Preferences.
The window
A window that paints into the title bar region, with Avalloy's title bar across the top. On macOS the traffic lights overlay the far left; on Windows and Linux the caption buttons sit at the right, and RightContent is kept clear of them automatically.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="using:Avalloy.Controls.TitleBar"
xmlns:ap="using:Avalloy.AttachedProperties"
xmlns:icons="using:Avalloy.Icons"
x:Class="MyApp.MainWindow"
Background="{DynamicResource Surface.Window}"
ExtendClientAreaToDecorationsHint="True"
ap:MacOSTitleBar.IsThick="True">
<Grid RowDefinitions="44,*">
<tb:AlloyTitleBar Grid.Row="0" MinHeight="44" Padding="0"
Background="{DynamicResource Surface.Window}">
<TextBlock Text="My App" Classes="secondary"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<tb:AlloyTitleBar.RightContent>
<icons:GlyphIcon Glyph="moon" Size="15"/>
</tb:AlloyTitleBar.RightContent>
</tb:AlloyTitleBar>
<!-- your content -->
</Grid>
</Window>Derive the window from ShellWindow instead of Window when you want the keyboard dispatcher wired in: the tunnelled key handler, the command surface for toolbars and the palette, and the macOS activation plumbing. See Windows and the title bar.
Where to next
- The starter app is all of the above, plus a sidebar, commands, a splash, and settings. Copy it and rename it.
- Core is the tour of the app shell.
- Themes explains the token contract and how to make the templates your brand.