Creating a Theme
In a UraniumUI app, a theme is a set of .NET MAUI resource dictionaries that define your app's colors, base styles, style classes, and reusable visual patterns. You do not need to create another library to theme an app. Most apps can keep the Material controls and replace the parts that make the UI feel like your product.
This page is a handbook for app developers who want to create their own visual system on top of UraniumUI.
Theme Scope
Choose the smallest scope that solves the problem.
| Goal | Recommended approach |
|---|---|
| Change app colors | Provide a colors dictionary through material:StyleResource ColorsOverride. |
| Change existing Material styles | Use material:StyleResource.Overrides for existing keyed styles. |
| Add app-specific visual variants | Add your own StyleClass styles after the theme resources are merged. |
| Style a group of child controls together | Use CascadingStyle.Resources and CascadingStyle.StyleClass. |
| Build reusable field or button controls | Compose UraniumUI primitives such as InputField, StatefulContentView, ButtonView, Select, and EntryView. |
Start with colors and styles in your app. Create custom controls only when a repeated pattern needs behavior, bindable properties, validation integration, or a cleaner API for page authors.
Runtime Setup
Every app that uses UraniumUI controls should register the core handlers:
builder
.UseMauiApp<App>()
.UseUraniumUI();
Apps that use the Material theme or Material controls should also register Material:
builder
.UseMauiApp<App>()
.UseUraniumUI()
.UseUraniumUIMaterial();
UseUraniumUI() registers core handlers and dependencies, including StatefulContentView, Select, Dropdown, InputKit handlers, and Plainer controls. UseUraniumUIMaterial() registers Material-specific handlers and configures AutoFormView to use Material editors.
Resource Setup
Material resources are exposed through the Material XAML namespace:
xmlns:material="http://schemas.enisn-projects.io/dotnet/maui/uraniumui/material"
The common app setup is:
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:material="http://schemas.enisn-projects.io/dotnet/maui/uraniumui/material"
x:Class="MyApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary x:Name="appColors" Source="Resources/Themes/AppColors.xaml" />
<ResourceDictionary x:Name="appBaseStyles" Source="Resources/Themes/AppBaseStyles.xaml" />
<material:StyleResource
ColorsOverride="{x:Reference appColors}"
BasedOn="{x:Reference appBaseStyles}">
<material:StyleResource.Overrides>
<ResourceDictionary Source="Resources/Themes/AppMaterialOverrides.xaml" />
</material:StyleResource.Overrides>
</material:StyleResource>
<ResourceDictionary Source="Resources/Themes/AppStyleClasses.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Use this order deliberately:
AppColors.xamlprovides color keys thatColorsOverridecan copy into Material's color dictionary.AppBaseStyles.xamlcan provide keyed base styles that match Material style keys; Material styles inherit missing setters from those styles.AppMaterialOverrides.xamlcan override setters on existing Material keyed styles with matching keys.AppStyleClasses.xamlcan add new app-specific styles after Material resources exist.
StyleResource.Overrides changes existing theme styles. It is not a general merge point for new styles. Put new styles in a normal merged dictionary after material:StyleResource.
Color Tokens
UraniumUI Material uses named color tokens and the dark-token suffix convention. The light token is named Primary; the dark token is named PrimaryDark. Styles bind both with AppThemeBinding:
<Setter Property="TextColor"
Value="{AppThemeBinding Light={StaticResource OnSurface}, Dark={StaticResource OnSurfaceDark}}" />
Use these token groups when designing a palette:
| Tokens | Purpose |
|---|---|
Primary, OnPrimary, PrimaryContainer, OnPrimaryContainer |
Main brand actions and high-emphasis surfaces. |
Secondary, OnSecondary, SecondaryContainer, OnSecondaryContainer |
Supporting actions and lower-emphasis brand surfaces. |
Tertiary, OnTertiary, TertiaryContainer, OnTertiaryContainer |
Accent areas and alternate selection states. |
Surface, OnSurface, SurfaceVariant, OnSurfaceVariant |
Cards, sheets, dialogs, inputs, dividers, and text on surfaces. |
Background, OnBackground |
Page-level backgrounds and default text. |
Error, ErrorContainer, OnError, OnErrorContainer |
Validation and destructive/error states. |
Outline, OutlineVariant |
Borders, separators, and low-emphasis strokes. |
Shadow, Scrim, InverseSurface, InverseOnSurface, InversePrimary |
Elevation, overlays, and inverse surfaces. |
A minimal color override file should keep the same keys that Material expects:
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Color x:Key="Primary">#2458D3</Color>
<Color x:Key="PrimaryDark">#ADC6FF</Color>
<Color x:Key="OnPrimary">#FFFFFF</Color>
<Color x:Key="OnPrimaryDark">#082B73</Color>
<Color x:Key="Surface">#FCFBFF</Color>
<Color x:Key="SurfaceDark">#111318</Color>
<Color x:Key="OnSurface">#1B1B21</Color>
<Color x:Key="OnSurfaceDark">#E4E2EA</Color>
<Color x:Key="Background">#F7F8FF</Color>
<Color x:Key="BackgroundDark">#0C0E13</Color>
<Color x:Key="OnBackground">#1B1B21</Color>
<Color x:Key="OnBackgroundDark">#E4E2EA</Color>
<Color x:Key="Error">#BA1A1A</Color>
<Color x:Key="ErrorDark">#FFB4AB</Color>
<Color x:Key="Outline">#74777F</Color>
<Color x:Key="OutlineDark">#8E9099</Color>
</ResourceDictionary>
Keep contrast in mind when choosing every On... token. OnPrimary must be readable on Primary, OnSurface must be readable on Surface, and validation text must remain readable in both light and dark themes.
Style Layers
App theme styles normally use three layers.
| Layer | Example | Usage |
|---|---|---|
| Keyed base style | x:Key="UraniumUI.Styles.Button.Filled" |
Stable extension point for app-level overrides. |
| Implicit style | <Style TargetType="Label" /> |
Default appearance for a type. |
| Class style | Class="FilledButton" |
Variant selected by StyleClass. |
The Material theme defines keyed styles first, then maps built-in StyleClass names to those keyed styles:
<Style x:Key="UraniumUI.Styles.Button.Filled"
TargetType="Button"
BaseResourceKey="BaseButtonStyle"
CanCascade="True">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource OnPrimary}, Dark={StaticResource OnPrimaryDark}}" />
</Style>
<Style BaseResourceKey="UraniumUI.Styles.Button.Filled"
TargetType="Button"
Class="FilledButton"
CanCascade="True" />
That lets app code stay simple:
<Button Text="Save" StyleClass="FilledButton" />
It also gives your app a stable style key to override:
<Style x:Key="UraniumUI.Styles.Button.Filled" TargetType="Button">
<Setter Property="CornerRadius" Value="14" />
<Setter Property="HeightRequest" Value="44" />
</Style>
Use BaseResourceKey instead of copying large styles. This keeps future theme fixes and state definitions intact unless you intentionally replace them.
Material Style Overrides
Use material:StyleResource.Overrides when you want to change existing Material keyed styles.
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:material="http://schemas.enisn-projects.io/dotnet/maui/uraniumui/material">
<Style x:Key="UraniumUI.Styles.InputField"
TargetType="material:InputField"
ApplyToDerivedTypes="True">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="InputBackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark={StaticResource SurfaceDark}}" />
</Style>
<Style x:Key="UraniumUI.Styles.TextField" TargetType="material:TextField">
<Setter Property="SelectionHighlightColor" Value="{StaticResource Primary}" />
</Style>
</ResourceDictionary>
Only setters are merged into the existing Material style. If the override contains a setter for a property that already exists, the value is replaced. If it contains a new setter, the setter is added.
New Style Classes
Add new class styles in a normal dictionary after the theme is merged:
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style TargetType="Button"
Class="DangerButton"
BaseResourceKey="UraniumUI.Styles.Button.Filled">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Error}, Dark={StaticResource ErrorDark}}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource OnError}, Dark={StaticResource OnErrorDark}}" />
</Style>
<Style TargetType="Border" Class="Card" BaseResourceKey="UraniumUI.Styles.Border.Rounded">
<Setter Property="Padding" Value="16" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark={StaticResource SurfaceDark}}" />
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource OutlineVariant}, Dark={StaticResource OutlineVariantDark}}" />
</Style>
</ResourceDictionary>
Usage:
<Button Text="Delete" StyleClass="DangerButton" />
<Border StyleClass="Card">
<Label Text="Account details" />
</Border>
Use class styles for variants that developers select explicitly. Use implicit styles only when every control of that type should change.
Inherited Styles
Use BaseResourceKey to create variants from existing styles:
<Style TargetType="Button"
Class="CompactFilledButton"
BaseResourceKey="UraniumUI.Styles.Button.Filled">
<Setter Property="HeightRequest" Value="34" />
<Setter Property="Padding" Value="14,0" />
</Style>
Use ApplyToDerivedTypes="True" when a base control style should also apply to subclasses:
<Style x:Key="App.Styles.InputField"
TargetType="material:InputField"
ApplyToDerivedTypes="True">
<Setter Property="CornerRadius" Value="10" />
</Style>
Use CanCascade="True" on styles that should participate in UraniumUI cascading style behavior.
Cascading Styles
Cascading styles are useful when a container should set the appearance of its children without requiring every child to specify a StyleClass.
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:uranium="http://schemas.enisn-projects.io/dotnet/maui/uraniumui">
<Style TargetType="Border" Class="PrimaryCard" BaseResourceKey="UraniumUI.Styles.Border.Rounded" CanCascade="True">
<Setter Property="Padding" Value="20" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource PrimaryContainer}, Dark={StaticResource PrimaryContainerDark}}" />
<Setter Property="uranium:CascadingStyle.Resources">
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource OnPrimaryContainer}, Dark={StaticResource OnPrimaryContainerDark}}" />
</Style>
<Style TargetType="Button">
<Setter Property="uranium:CascadingStyle.StyleClass" Value="TextButton" />
</Style>
</ResourceDictionary>
</Setter>
</Style>
</ResourceDictionary>
Usage:
<Border StyleClass="PrimaryCard">
<VerticalStackLayout Spacing="12">
<Label Text="Subscription" />
<Button Text="Manage" />
</VerticalStackLayout>
</Border>
See Cascading Styling for the full cascading style API.
App Control Primitives
App themes should prefer composition over platform-specific handlers. UraniumUI already provides primitives that solve common interaction and accessibility problems.
| Primitive | Use it for |
|---|---|
InputField |
A Material field shell with floating label, border, icon, attachments, focus state, and validation area. |
TextField |
Text input built from InputField plus a chrome-free EntryView. Use it directly or copy its pattern for custom text fields. |
StatefulContentView |
Clickable custom surfaces with pressed, pointer-over, focus, and command support. |
ButtonView |
Material clickable content when a normal MAUI Button is not flexible enough. |
Select and Dropdown |
Selection and overlay patterns that need keyboard-aware behavior. |
CascadingStyle |
Container-level child styling and style-class assignment. |
For example, a fully custom text input does not need a new handler just to draw a border. Wrap a chrome-free entry in a Border, or use InputField when you need the Material floating label and validation behavior. See Building Theme Controls for advanced examples.
App Theme Folder Structure
Keep app theme files close to the MAUI resource files that developers already know. A practical structure is:
Resources/
Themes/
AppColors.xaml
AppBaseStyles.xaml
AppMaterialOverrides.xaml
AppStyleClasses.xaml
Styles/
Colors.xaml
Styles.xaml
Use Resources/Styles/Colors.xaml and Resources/Styles/Styles.xaml for ordinary MAUI starter-template resources. Use Resources/Themes for UraniumUI-specific tokens, Material overrides, and style classes. This separation makes it easier to see which files are part of your UraniumUI visual system.
If your app only needs a small color change, you may only need AppColors.xaml. Add the other files when the app starts to need shared button variants, card styles, input-field changes, or custom control styles.
Accessibility Contract
Accessibility is part of the app theme contract. Every interactive style and custom control should preserve these behaviors:
| Area | Requirement |
|---|---|
| Keyboard | Interactive controls are reachable by Tab and activate with the expected keys. |
| Focus | Focus state is visible in light and dark themes. |
| Pointer states | Hover, pressed, disabled, selected, and error states remain visually distinct. |
| Screen readers | Icon-only actions have SemanticProperties.Description and useful hints. |
| Validation | Error messages are visible as text and not communicated only by color or an icon. |
| Contrast | Text and icons have sufficient contrast against their container tokens. |
Use StatefulContentView or ButtonView for custom clickable surfaces instead of a passive layout with only TapGestureRecognizer. See Accessibility Best Practices and Clickable Areas.