A library for adding Material You colors to your .NET MAUI app
- Dynamic theming on every platform (except iOS)
- Light/dark theme support
- Automatically storing and reapplying seed color/dark mode/style preferences
All of these can be turned on/off at any time.
- Add a reference to the package. For instructions visit the NuGet page.
- Call the
UseMaterialColors
extension method inMauiProgram.cs
. This will add all the services required for theming to the service container.
+using MaterialColorUtilities.Maui;
namespace YourApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
MauiAppBuilder builder = MauiApp
.CreateBuilder()
+ .UseMaterialColors()
.UseMauiApp<App>();
return builder.Build();
}
}
You can specify the fallback seed as an argument to the extension method or use a lambda for more options:
.UseMaterialColors(options =>
{
options.FallbackSeed = 0xB000B5;
options.UseDynamicColor = false;
})
- Initialize MaterialColorService with a ResourceDictionary:
If you use XAML click here
Copy these lines:
<Color x:Key="Primary" />
<Color x:Key="PrimaryContainer" />
<Color x:Key="Secondary" />
<Color x:Key="SecondaryContainer" />
<Color x:Key="Tertiary" />
<Color x:Key="TertiaryContainer" />
<Color x:Key="Surface" />
<Color x:Key="SurfaceVariant" />
<Color x:Key="Background" />
<Color x:Key="Error" />
<Color x:Key="ErrorContainer" />
<Color x:Key="OnPrimary" />
<Color x:Key="OnPrimaryContainer" />
<Color x:Key="OnSecondary" />
<Color x:Key="OnSecondaryContainer" />
<Color x:Key="OnTertiary" />
<Color x:Key="OnTertiaryContainer" />
<Color x:Key="OnSurface" />
<Color x:Key="OnSurfaceVariant" />
<Color x:Key="OnError" />
<Color x:Key="OnErrorContainer" />
<Color x:Key="OnBackground" />
<Color x:Key="Outline" />
<Color x:Key="Shadow" />
<Color x:Key="InverseSurface" />
<Color x:Key="InverseOnSurface" />
<Color x:Key="InversePrimary" />
<Color x:Key="Surface1" />
<Color x:Key="Surface2" />
<Color x:Key="Surface3" />
<Color x:Key="Surface4" />
<Color x:Key="Surface5" />
<SolidColorBrush x:Key="PrimaryBrush" />
<SolidColorBrush x:Key="PrimaryContainerBrush" />
<SolidColorBrush x:Key="SecondaryBrush" />
<SolidColorBrush x:Key="SecondaryContainerBrush" />
<SolidColorBrush x:Key="TertiaryBrush" />
<SolidColorBrush x:Key="TertiaryContainerBrush" />
<SolidColorBrush x:Key="SurfaceBrush" />
<SolidColorBrush x:Key="SurfaceVariantBrush" />
<SolidColorBrush x:Key="BackgroundBrush" />
<SolidColorBrush x:Key="ErrorBrush" />
<SolidColorBrush x:Key="ErrorContainerBrush" />
<SolidColorBrush x:Key="OnPrimaryBrush" />
<SolidColorBrush x:Key="OnPrimaryContainerBrush" />
<SolidColorBrush x:Key="OnSecondaryBrush" />
<SolidColorBrush x:Key="OnSecondaryContainerBrush" />
<SolidColorBrush x:Key="OnTertiaryBrush" />
<SolidColorBrush x:Key="OnTertiaryContainerBrush" />
<SolidColorBrush x:Key="OnSurfaceBrush" />
<SolidColorBrush x:Key="OnSurfaceVariantBrush" />
<SolidColorBrush x:Key="OnErrorBrush" />
<SolidColorBrush x:Key="OnErrorContainerBrush" />
<SolidColorBrush x:Key="OnBackgroundBrush" />
<SolidColorBrush x:Key="OutlineBrush" />
<SolidColorBrush x:Key="ShadowBrush" />
<SolidColorBrush x:Key="InverseSurfaceBrush" />
<SolidColorBrush x:Key="InverseOnSurfaceBrush" />
<SolidColorBrush x:Key="InversePrimaryBrush" />
<SolidColorBrush x:Key="Surface1Brush" />
<SolidColorBrush x:Key="Surface2Brush" />
<SolidColorBrush x:Key="Surface3Brush" />
<SolidColorBrush x:Key="Surface4Brush" />
<SolidColorBrush x:Key="Surface5Brush" />
and update App.xaml
like this:
<?xml version="1.0" encoding="UTF-8" ?>
<Application
x:Class="Playground.Maui.App"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:mcu="clr-namespace:MaterialColorUtilities.Maui;assembly=MaterialColorUtilities.Maui">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
+ <ResourceDictionary>
+ <!-- Copied lines go here -->
+ </ResourceDictionary>
+ <mcu:MaterialColorResourceDictionary />
<!-- ResourceDictionaries that use Material colors can go here -->
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Your styles that use Material colors can go here -->
<Style TargetType="Button">
<Setter Property="BackgroundColor" Value="{DynamicResource Primary}" />
<Setter Property="TextColor" Value="{DynamicResource OnPrimary}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
If you don't use XAML click here
Edit App.xaml.cs
like this:
+using MaterialColorUtilities.Maui;
public class App : Application
{
public App()
{
+ IMaterialColorService.Current.Initialize(this.Resources);
MainPage = new AppShell();
}
}
By default the colors are added as global resources. You can access them
- in XAML using DynamicResource:
<Button BackgroundColor="{DynamicResource Primary}" />
- in C# using
Element.SetDynamicResource()
:
Button button = new();
button.SetDynamicResource(Button.BackgroundProperty, "Primary");
The MaterialColorService
can be resolved from the MAUI dependency injection container. It contains the current core palette and schemes. It also has properties for updating the seed color and style.
using MaterialColorUtilites.Maui;
public class MyService
{
public MyService(MaterialColorService materialColorService)
{
Scheme<Color> scheme = materialColorService.SchemeMaui;
materialColorService.Seed = 0x123456;
materialColorService.Style = Style.Spritz;
}
}
Extending MaterialColorService allows you to use your custom colors or set different outputs for theming.
When you do this, register the new service at startup:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
MauiAppBuilder builder = MauiApp
.CreateBuilder()
+ .UseMaterialColors<MyMaterialColorService>()
.UseMauiApp<App>();
return builder.Build();
}
}
If you want to use custom colors, follow the instructions here on how to create you own palettes/schemes. Then specify them as generic type arguments:
public class MyMaterialColorService : MaterialColorService<MyCorePalette, MyScheme<uint>, MyScheme<Color>, MyLightSchemeMapper, MyDarkSchemeMapper>
{
public MyMaterialColorService(IOptions<MaterialColorOptions> options, IDynamicColorService dynamicColorService, IPreferences preferences)
: base(options, dynamicColorService, preferences)
{
}
}
By overriding the Apply method you can define things that should happen when colors change. For example invoking an event, setting native colors or if you are using .NET MAUI with Blazor, updating CSS.
public class MyMaterialColorService : MaterialColorService
{
private readonly WeakEventManager _weakEventManager = new();
public MyMaterialColorService(IOptions<MaterialColorOptions> options, IDynamicColorService dynamicColorService, IPreferences preferences) : base(options, dynamicColorService, preferences)
{
}
public event EventHandler SeedChanged
{
add => _weakEventManager.AddEventHandler(value);
remove => _weakEventManager.RemoveEventHandler(value);
}
protected override async void Apply()
{
base.Apply();
_weakEventManager.HandleEvent(null!, null!, nameof(SeedChanged));
#if ANDROID
Activity activity = await Platform.WaitForActivityAsync();
// Update status/navigation bar background color
Android.Graphics.Color androidColor = SchemeMaui.Surface2.ToPlatform();
activity.Window!.SetNavigationBarColor(androidColor);
activity.Window!.SetStatusBarColor(androidColor);
// Update status/navigation bar text/icon color
_ = new WindowInsetsControllerCompat(activity.Window, activity.Window.DecorView)
{
AppearanceLightStatusBars = !IsDark,
AppearanceLightNavigationBars = !IsDark
};
#endif
}
}
Light/dark mode handling works everywhere, but the dynamic theming source is different on all of the platforms:
- Android ≥12.0 (API 31): use system colors
- Android ≥8.1 (API 27): use WallpaperColors.Primary as seed color
- Android ≥7.0 (API 24): if the
StorageRead
permission has been granted, extract a color from the wallpaper, otherwise use fallback seed - Android ≥5.0 (API 21): use fallback seed
- iOS: no accent color, use fallback seed
- Mac: use accent color
- Windows: use accent color