Custom .NET MAUI backends targeting platforms not officially supported by MAUI — Apple TV (tvOS via UIKit) and macOS (native AppKit, not Mac Catalyst).
Both backends use the platform-agnostic MAUI NuGet packages (net10.0 fallback assemblies) and provide custom handler implementations that bridge MAUI's layout/rendering system to the native platform UI frameworks.
Inspiration: The Xamarin.Forms project previously had a macOS AppKit backend (Xamarin.Forms.Platform.MacOS). While this project uses MAUI's handler architecture rather than the legacy renderer approach, some of that codebase is useful as a reference for mapping AppKit native controls.
MAUI-macOS-Demo.mov
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-macos</TargetFramework>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<SupportedOSPlatformVersion>14.0</SupportedOSPlatformVersion>
<ApplicationTitle>My macOS App</ApplicationTitle>
<ApplicationId>com.example.myapp</ApplicationId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Platform.Maui.MacOS" Version="*" />
<PackageReference Include="Platform.Maui.Essentials.MacOS" Version="*" />
</ItemGroup>
<!-- App icon (SVG or PNG source — .icns is generated automatically) -->
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.png" />
</ItemGroup>
</Project>using AppKit;
public class MainClass
{
static void Main(string[] args)
{
NSApplication.Init();
NSApplication.SharedApplication.Delegate = new MauiMacOSApp();
NSApplication.Main(args);
}
}using Foundation;
using Microsoft.Maui.Platform.MacOS;
[Register("MauiMacOSApp")]
public class MauiMacOSApp : MacOSMauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}using Microsoft.Maui.Platform.MacOS.Hosting;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiAppMacOS<App>()
.AddMacOSEssentials()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}public class App : Application
{
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new MainPage());
}The Platform.Maui.MacOS NuGet package includes MSBuild targets that automatically process MauiIcon items — the same item type used by MAUI's Resizetizer for iOS, Android, and Windows.
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.png" />
</ItemGroup>At build time, the targets:
- Use
sipsto resize the source image (SVG or PNG) to all 10 required macOS icon sizes (16×16 through 512×512@2x) - Use
iconutilto create the.icnsfile - Inject
CFBundleIconFileinto the app manifest viaPartialAppManifest - Add the
.icnsas aBundleResource
No manual .icns creation or Info.plist editing required.
The macOS backend supports Shell with a native macOS sidebar using NSOutlineView source list, providing a Finder/System Settings-like navigation experience. Configure it with attached properties on your Shell:
using Microsoft.Maui.Platform.MacOS;
public class AppShell : Shell
{
public AppShell()
{
FlyoutBehavior = FlyoutBehavior.Locked;
// Enable native NSOutlineView sidebar
MacOSShell.SetUseNativeSidebar(this, true);
// Allow user to resize sidebar by dragging the divider (default: true)
MacOSShell.SetIsSidebarResizable(this, true);
var generalSection = new FlyoutItem { Title = "General" };
var home = new ShellContent { Title = "Home", ContentTemplate = new DataTemplate(typeof(HomePage)) };
MacOSShell.SetSystemImage(home, "house.fill"); // SF Symbol icon
generalSection.Items.Add(home);
var settings = new ShellContent { Title = "Settings", ContentTemplate = new DataTemplate(typeof(SettingsPage)) };
MacOSShell.SetSystemImage(settings, "gear");
generalSection.Items.Add(settings);
Items.Add(generalSection);
}
}| Property | Type | Default | Description |
|---|---|---|---|
UseNativeSidebar |
bool |
false |
Use native NSOutlineView source list sidebar instead of custom-drawn sidebar |
SystemImage |
string? |
null |
SF Symbol name for sidebar icon (e.g. "house.fill", "gear", "star"). Set on ShellContent, ShellSection, or FlyoutItem |
IsSidebarResizable |
bool |
true |
Allow sidebar resizing by dragging the divider |
For FlyoutPage, you can also use a native sidebar with structured items:
using Microsoft.Maui.Platform.MacOS;
var flyoutPage = new FlyoutPage();
MacOSFlyoutPage.SetSidebarItems(flyoutPage, new List<MacOSSidebarItem>
{
new MacOSSidebarItem
{
Title = "Library",
Children = new List<MacOSSidebarItem>
{
new MacOSSidebarItem { Title = "Music", SystemImage = "music.note" },
new MacOSSidebarItem { Title = "Photos", SystemImage = "photo" },
}
}
});
MacOSFlyoutPage.SetSidebarSelectionChanged(flyoutPage, item =>
{
// Handle selection
});To use the native sidebar handler, register it in MauiProgram.cs:
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<FlyoutPage, NativeSidebarFlyoutPageHandler>();
});| Property | Type | Default | Description |
|---|---|---|---|
SidebarItems |
IList<MacOSSidebarItem>? |
null |
Structured sidebar items for native NSOutlineView |
SidebarSelectionChanged |
Action<MacOSSidebarItem>? |
null |
Callback when a sidebar item is selected |
| Property | Type | Description |
|---|---|---|
Title |
string |
Display title |
SystemImage |
string? |
SF Symbol name (takes priority over Icon) |
Icon |
ImageSource? |
MAUI ImageSource fallback |
Children |
IList<MacOSSidebarItem>? |
Child items — makes this item a section header (group row) |
Tag |
object? |
Developer-defined tag for identifying selected items |
IsGroup |
bool |
Read-only: true when item has children |
src/
Platform.Maui.MacOS/ # macOS AppKit backend library (net10.0-macos)
Platform.Maui.MacOS.BlazorWebView/ # Blazor Hybrid support for macOS
Platform.Maui.TvOS/ # tvOS backend library (net10.0-tvos)
Platform.Maui.Essentials.MacOS/ # macOS Essentials library
Platform.Maui.Essentials.TvOS/ # tvOS Essentials library
samples/
Sample/ # Shared sample code (Pages, Platforms/)
SampleTv/ # tvOS sample app
SampleMac/ # macOS sample app
Note: There is also a
Sample/Sample.csprojthat multitargets bothnet10.0-tvosandnet10.0-macos, but it is not yet working. UseSampleTvandSampleMacto build and run the individual platform samples.
| Control | tvOS (UIKit) | macOS (AppKit) |
|---|---|---|
| Label | UILabel | NSTextField (non-editable) |
| Button | UIButton | NSButton |
| ImageButton | — | NSButton (image content) |
| Entry | UITextField | NSTextField (editable) |
| Editor | — | NSTextView (in NSScrollView) |
| Picker | UIButton + UIAlertController | NSPopUpButton |
| Slider | Custom TvOSSliderView | NSSlider |
| Stepper | — | NSStepper |
| Switch | UIButton (toggle) | NSSwitch |
| CheckBox | — | NSButton (checkbox style) |
| RadioButton | — | NSButton (radio style) |
| SearchBar | Custom TvOSSearchBarView | NSSearchField |
| ActivityIndicator | UIActivityIndicatorView | NSProgressIndicator |
| ProgressBar | UIProgressView | NSProgressIndicator (bar) |
| Image | UIImageView | NSImageView |
| DatePicker | — | NSDatePicker (date mode) |
| TimePicker | — | NSDatePicker (time mode) |
| WebView | — | WKWebView |
| MapView | MKMapView (display-only) | MKMapView (interactive) |
| Page | tvOS | macOS |
|---|---|---|
| ContentPage | ✅ | ✅ |
| NavigationPage | ✅ Stack navigation | ✅ Stack + toolbar back button |
| TabbedPage | ✅ Custom tab bar | ✅ NSSegmentedControl |
| FlyoutPage | — | ✅ Native sidebar or custom |
| Shell | — | ✅ Full navigation, native sidebar, push/pop |
| Modal pages | — | ✅ NSAlert-backed |
| Control | tvOS | macOS |
|---|---|---|
| CollectionView | ✅ Virtualized | ✅ Virtualized, grouping, selection, EmptyView, Header/Footer |
| CarouselView | ✅ Horizontal paging | ✅ Scroll snapping, indicators |
| ListView | — | ✅ (deprecated — use CollectionView) |
| TableView | — | ✅ |
| IndicatorView | — | ✅ |
| RefreshView | — | ✅ |
| SwipeView | — | ✅ |
All layouts work on both platforms: VerticalStackLayout, HorizontalStackLayout, Grid, FlexLayout, AbsoluteLayout, ScrollView, ContentView, Border, Frame.
| Feature | tvOS | macOS |
|---|---|---|
| GraphicsView | — | ✅ CoreGraphics DirectRenderer |
| Shapes (Rectangle, Ellipse, Line, Polyline, Polygon, Path) | ✅ | ✅ |
| Shadow | ✅ | ✅ |
| Gradient Brushes (Linear, Radial) | — | ✅ via CAGradientLayer |
| Gesture | tvOS | macOS |
|---|---|---|
| Tap | ✅ | ✅ |
| Pan | — | ✅ |
| Swipe | — | ✅ |
| Pinch | — | ✅ |
| Pointer (Hover) | — | ✅ |
| Component | tvOS | macOS |
|---|---|---|
| Application | NSObject | NSObject |
| Window | UIWindow + UIViewController | NSWindow + FlippedNSView |
| Dispatcher | GCD (DispatchQueue.MainQueue) | GCD (DispatchQueue.MainQueue) |
| DispatcherTimer | NSTimer | NSTimer |
| Dialogs (Alert, Confirm, Prompt) | — | ✅ NSAlert |
| Font Management | ✅ | ✅ CTFontManager |
| Dark/Light Mode | ✅ | ✅ Automatic theme detection |
| Animations | ✅ | ✅ MacOSTicker + MAUI animation system |
| FormattedText / Spans | — | ✅ NSAttributedString |
| InputTransparent | — | ✅ HitTest override |
| Toolbar | — | ✅ NSToolbar |
| Window Resize Relayout | — | ✅ |
The BlazorWebView implementation uses a custom MacOSBlazorWebView control — the built-in MAUI BlazorWebView internally casts to the iOS/Catalyst handler, which fails on AppKit.
// In MauiProgram.cs
builder.AddMacOSBlazorWebView();using Microsoft.Maui.Platform.MacOS.Controls;
var blazorView = new MacOSBlazorWebView
{
HostPage = "wwwroot/index.html",
HeightRequest = 400
};
blazorView.RootComponents.Add(new BlazorRootComponent
{
Selector = "#app",
ComponentType = typeof(MyApp.Components.Counter)
});Static web assets (wwwroot/) must be included as BundleResource items in the project file:
<ItemGroup>
<BundleResource Include="wwwroot\**" />
</ItemGroup>Both platforms fire standard IWindow lifecycle methods and support MAUI's ConfigureLifecycleEvents builder pattern.
| IWindow Method | macOS Trigger | tvOS Trigger |
|---|---|---|
Created() |
App launch | App launch |
Activated() |
App becomes active | App becomes active |
Deactivated() |
App loses active status | App resigns activation |
Stopped() |
App hidden (Cmd+H) | App enters background |
Resumed() |
App unhidden | App enters foreground |
Destroying() |
App terminating | App terminating |
builder.ConfigureLifecycleEvents(events =>
{
events.AddMacOS(macOS => macOS
.DidFinishLaunching(notification => { /* NSNotification */ })
.DidBecomeActive(notification => { })
.DidResignActive(notification => { })
.DidHide(notification => { })
.DidUnhide(notification => { })
.WillTerminate(notification => { })
);
});Platform-specific implementations of MAUI Essentials APIs.
// In MauiProgram.cs
builder.AddMacOSEssentials();Then use the standard MAUI Essentials APIs:
var appName = AppInfo.Name;
var version = AppInfo.VersionString;
var theme = AppInfo.RequestedTheme;
var model = DeviceInfo.Model;| API | tvOS | macOS | Notes |
|---|---|---|---|
| AppInfo | ✅ | ✅ | Package name, version, build, theme, layout direction |
| DeviceInfo | ✅ | ✅ | Model, manufacturer, device name, OS version, platform, idiom |
| Connectivity | ✅ | ✅ | Network access status, connection profiles, change events |
| Battery | — | ✅ | Charge level, state, power source (IOKit) |
| DeviceDisplay | ✅ | ✅ | Screen dimensions, density, orientation, refresh rate |
| FileSystem | ✅ | ✅ | Cache directory, app data directory, app package files |
| Preferences | ✅ | ✅ | Key/value storage via NSUserDefaults |
| SecureStorage | ✅ | ✅ | Encrypted storage via Keychain |
| FilePicker | — | ✅ | NSOpenPanel file selection |
| MediaPicker | — | ✅ | Photo/video picking via NSOpenPanel |
| TextToSpeech | ✅ | ✅ | macOS: NSSpeechSynthesizer, tvOS: AVSpeechSynthesizer |
| Clipboard | ✅ | ✅ | macOS: NSPasteboard, tvOS: in-process |
| Browser | — | ✅ | Open URLs via NSWorkspace |
| Share | — | ✅ | NSSharingServicePicker |
| Launcher | — | ✅ | Open files/URIs via NSWorkspace |
Important: JetBrains Rider and Visual Studio will not compile these projects. The
net10.0-tvosandnet10.0-macosTFMs are not recognized by IDE build systems. All building and running must be done through the CLI using thedotnetcommand.
Install the latest .NET 10 preview SDK from dotnet.microsoft.com.
dotnet workload install tvos
dotnet workload install macos
dotnet workload list # verifyXcode must be installed for Apple platform SDKs:
sudo xcode-select -s /Applications/Xcode.app# Build
dotnet build samples/SampleMac/SampleMac.csproj
dotnet build samples/SampleTv/SampleTv.csproj
# Run macOS
dotnet build samples/SampleMac/SampleMac.csproj -t:Run
# Run tvOS (simulator)
dotnet build samples/SampleTv/SampleTv.csproj -t:RunNote: Do not use
dotnet build "MAUI Platforms.slnx"— build projects individually.
- MAUI NuGet packages resolve to the
net10.0(platform-agnostic) assembly for unsupported TFMs.ToPlatform()returnsobject— customViewExtensionscasts to the native view type. - The platform-agnostic
ViewHandlerhas no-opPlatformArrangeand returnsSize.Zero. Custom base handlers (MacOSViewHandler/TvOSViewHandler) override these to bridge MAUI layout to native view frames. - macOS NSView uses bottom-left origin by default. All container views override
IsFlipped => truefor MAUI's top-left coordinate system. - macOS NSView has no
SizeThatFits()— the base handler usesIntrinsicContentSizeandFittingSize, with a customSizeThatFitsmethod onMacOSContainerView. - Sample apps use pure C# pages (no XAML) to avoid XAML compilation issues on unsupported platforms.
- The macOS window uses
FullSizeContentViewstyle withTitlebarAppearsTransparent = trueso the Shell sidebar extends under the titlebar with proper macOS vibrancy.
Dialogs (DisplayAlertAsync, DisplayPromptAsync) are supported on macOS via NSAlert, but are not yet supported on tvOS.
MAUI's dialog system uses an internal AlertManager with IAlertManagerSubscription. On macOS, we implement this via DispatchProxy reflection. tvOS uses AOT compilation which doesn't support DispatchProxy. Until IAlertManagerSubscription is made public in MAUI (see the proposal), tvOS dialog support is blocked.
- Multitarget
Sampleproject - XAML Compilation
- Multi-window support
- Accessibility support
- Focus Engine integration
- Top Shelf extensions
- TV remote menu button handling
- Touch Bar support
- Drag and drop
- Multiple windows
- NuGet packaging
- CI/CD pipeline