Skip to main content

Cross-platform architecture

Avalonia renders controls using Skia rather than wrapping native platform controls. This means your AXAML views, view models, and business logic all produce identical results on every platform. This page covers what you can share, what needs platform-specific handling, and how to choose the right approach.

Avalonia's approach to code sharing

Because Avalonia draws its own controls, you get a consistent look and behavior across Windows, macOS, Linux, iOS, Android, and the browser. In a typical Avalonia application, the following are fully shared:

The areas that sometimes require platform-specific code include:

  • Hardware sensors (GPS, accelerometer, gyroscope)
  • Push notifications
  • Bluetooth, camera, and biometrics
  • System tray and other OS-level shell integrations

For device APIs that Avalonia does not abstract, Microsoft.Maui.Essentials provides a common layer that works with Avalonia on .NET 8 and higher. Keep in mind that Maui.Essentials does not cover Linux, browser, or non-Catalyst macOS targets.

Structuring your solution

The standard Avalonia cross-platform template creates a set of projects designed for maximum code sharing:

ProjectPurpose
CoreViews, view models, business logic (shared by all platforms)
DesktopEntry point for Windows, macOS, and Linux
AndroidEntry point for Android
iOSEntry point for iOS, iPadOS, and Mac Catalyst
BrowserEntry point for WebAssembly

The core project contains the vast majority of your code. Platform-specific projects are thin entry points that reference the core. See Setting up a cross-platform solution for a full walkthrough.

Handling platform differences

When you do need platform-specific behavior, Avalonia and .NET offer four approaches, ordered from simplest to most flexible.

OnPlatform and OnFormFactor

For UI-level adjustments, use the OnPlatform or OnFormFactor markup extensions directly in AXAML:

<TextBlock Text="{OnPlatform 'Welcome', iOS='Welcome (iOS)', Android='Welcome (Android)'}"/>

OnPlatform targets a specific operating system, while OnFormFactor targets a device category such as Desktop or Mobile. See Platform-specific XAML for the full syntax.

Runtime detection

For simple branching in C# code, use the OperatingSystem class:

if (OperatingSystem.IsWindows())
{
// Windows-specific logic
}

This works everywhere without changes to your project structure. See Platform-specific .NET for the full API reference.

Conditional compilation

For code that calls platform-specific APIs, use C# preprocessor directives with OS-specific target frameworks:

#if ANDROID
var orientation = GetAndroidOrientation();
#elif IOS
var orientation = GetiOSOrientation();
#else
var orientation = DeviceOrientation.Undefined;
#endif

This requires your project to multi-target (for example, net8.0;net8.0-ios;net8.0-android). See Platform-specific .NET for setup details.

Interface abstraction

For complex platform features, define an interface in your shared project and implement it per platform:

// In Core project
public interface IDeviceOrientation
{
DeviceOrientation GetOrientation();
}

// In platform-specific project
public class AndroidDeviceOrientation : IDeviceOrientation
{
public DeviceOrientation GetOrientation() { /* Android APIs */ }
}

Register each implementation with your dependency injection container so that shared code can consume it without knowing which platform it runs on. See Platform-specific .NET for a complete example and Dependency injection for DI setup.

Choosing an approach

ApproachBest forTrade-off
OnPlatform / OnFormFactorUI tweaks (spacing, text, controls)XAML only
Runtime detectionSimple runtime branchingAll platform code ships in every binary
Conditional compilationPlatform-specific API callsRequires multi-targeting
Interface abstractionComplex platform featuresMore files, requires DI

Start with the simplest approach that meets your needs and move to a more flexible one only when necessary.

See also