{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/maui-theming",
  "version": "1.0.1",
  "name": "maui-theming",
  "description": "Guide for theming .NET MAUI apps — light/dark mode via AppThemeBinding, ResourceDictionary theme switching, DynamicResource bindings, system theme detection, and user theme preferences. Use when: \"dark mode\", \"light mode\", \"theming\", \"AppThemeBinding\", \"theme switching\", \"ResourceDictionary theme\", \"dynamic resources\", \"system theme detection\", \"color scheme\", \"app theme\", \"DynamicResource\". Do not use for: localization or language switching (see .NET MAUI localization documentation), accessibility visual adjustments (see .NET MAUI accessibility documentation), app icons or splash screens (see .NET MAUI app icons documentation), or Bootstrap-style class theming (see Plugin.Maui.BootstrapTheme NuGet package).",
  "system_prompt_fragment": "# .NET MAUI Theming\n\nApply light/dark mode support, custom branded themes, and runtime theme switching in .NET MAUI apps using AppThemeBinding, ResourceDictionary swapping, and system theme detection APIs.\n\n## When to Use\n\n- Adding light and dark mode support to a .NET MAUI app\n- Creating custom branded themes with ResourceDictionary\n- Detecting and responding to system theme changes at runtime\n- Letting users choose a preferred theme (light, dark, or system default)\n- Combining OS-driven theme response with custom color palettes\n\n## When Not to Use\n\n- Localization or language switching — see [.NET MAUI localization docs](https://learn.microsoft.com/dotnet/maui/fundamentals/localization)\n- Accessibility-specific visual adjustments — see [.NET MAUI accessibility docs](https://learn.microsoft.com/dotnet/maui/fundamentals/accessibility)\n- App icon or splash screen configuration — see [.NET MAUI app icon docs](https://learn.microsoft.com/dotnet/maui/user-interface/images/app-icons)\n- Bootstrap-style class theming — see the `Plugin.Maui.BootstrapTheme` NuGet package\n\n## Inputs\n\n- A .NET MAUI project targeting .NET 8 or later\n- XAML pages or C# UI code that need theme-aware styling\n\n## Workflow\n\n1. Detect the current theme approach in the project (AppThemeBinding, ResourceDictionary, or none).\n2. Choose the appropriate strategy: AppThemeBinding for simple light/dark, ResourceDictionary swap for custom/multiple themes, or both combined.\n3. Define theme resources — inline `AppThemeBinding` values or separate `ResourceDictionary` files with matching keys.\n4. Replace hardcoded colors with `DynamicResource` bindings (or `AppThemeBinding` markup) throughout XAML pages.\n5. Add system theme detection via `Application.Current.RequestedTheme` and the `RequestedThemeChanged` event.\n6. Implement user preference persistence with `Preferences.Set` / `Preferences.Get` and apply on startup.\n7. Verify Android `ConfigChanges.UiMode` is set on `MainActivity` to avoid activity restarts on theme change.\n8. Test both light and dark themes on at least one target platform, confirming all UI elements respond correctly.\n\n## Choosing an Approach\n\n| Approach | Best for | Limitation |\n|----------|----------|------------|\n| **AppThemeBinding** | Automatic light/dark with OS — minimal code | Only two themes (light + dark) |\n| **ResourceDictionary swap** | Custom branded themes, more than two themes, user preference | More setup; must use `DynamicResource` everywhere |\n| **Both combined** | OS-driven response plus custom theme colors | Most flexible but most complex |\n\n## AppThemeBinding (OS Light/Dark)\n\n`AppThemeBinding` selects a value based on the current system theme. It supports `Light`, `Dark`, and an optional `Default` fallback.\n\n### XAML\n\n```xml\n<Label Text=\"Themed text\"\n       TextColor=\"{AppThemeBinding Light=Green, Dark=Red}\"\n       BackgroundColor=\"{AppThemeBinding Light=White, Dark=Black}\" />\n\n<!-- With resource references -->\n<Label TextColor=\"{AppThemeBinding Light={StaticResource LightPrimary},\n                                   Dark={StaticResource DarkPrimary}}\" />\n```\n\n### C# Extension Methods\n\n```csharp\nvar label = new Label();\n\n// Color-specific helper\nlabel.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red);\n\n// Generic helper for any bindable property type\nlabel.SetAppTheme<Color>(Label.TextColorProperty, Colors.Green, Colors.Red);\n```\n\n## ResourceDictionary Theming (Custom Themes)\n\nUse separate `ResourceDictionary` files with matching keys to define themes, then swap them at runtime.\n\n### Step 1 — Define Theme Dictionaries\n\nWhen using compiled XAML with `x:Class` (as shown below), each dictionary needs a code-behind that calls `InitializeComponent()`. Dictionaries loaded via `Source` without `x:Class` do not need code-behind.\n\n**LightTheme.xaml**\n\n```xml\n<ResourceDictionary xmlns=\"http://schemas.microsoft.com/dotnet/2021/maui\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"\n                    x:Class=\"MyApp.Themes.LightTheme\">\n    <Color x:Key=\"PageBackgroundColor\">White</Color>\n    <Color x:Key=\"PrimaryTextColor\">#333333</Color>\n    <Color x:Key=\"AccentColor\">#2196F3</Color>\n</ResourceDictionary>\n```\n\n**LightTheme.xaml.cs**\n\n```csharp\nnamespace MyApp.Themes;\n\npublic partial class LightTheme : ResourceDictionary\n{\n    public LightTheme() => InitializeComponent();\n}\n```\n\nCreate a matching **DarkTheme.xaml / DarkTheme.xaml.cs** with the same keys and different values.\n\n### Step 2 — Consume with DynamicResource\n\nUse `DynamicResource` so values update when the dictionary is swapped at runtime:\n\n```xml\n<ContentPage BackgroundColor=\"{DynamicResource PageBackgroundColor}\">\n    <Label Text=\"Hello\"\n           TextColor=\"{DynamicResource PrimaryTextColor}\" />\n    <Button Text=\"Action\"\n            BackgroundColor=\"{DynamicResource AccentColor}\" />\n</ContentPage>\n```\n\n### Step 3 — Switch Themes at Runtime\n\n```csharp\nvoid ApplyTheme(ResourceDictionary theme)\n{\n    // Assumes theme dictionaries are the only merged dictionaries.\n    // If your App.xaml merges non-theme dictionaries (e.g., converters),\n    // move them to Application.Resources directly instead.\n    var mergedDictionaries = Application.Current!.Resources.MergedDictionaries;\n    mergedDictionaries.Clear();\n    mergedDictionaries.Add(theme);\n}\n\n// Usage\nApplyTheme(new DarkTheme());\n```\n\n## System Theme Detection\n\n### Read the Current Theme\n\n```csharp\nAppTheme currentTheme = Application.Current!.RequestedTheme;\n// Returns AppTheme.Light, AppTheme.Dark, or AppTheme.Unspecified\n```\n\n### Override the System Theme\n\n```csharp\n// Force dark mode regardless of OS setting\nApplication.Current!.UserAppTheme = AppTheme.Dark;\n\n// Reset to follow system theme\nApplication.Current!.UserAppTheme = AppTheme.Unspecified;\n```\n\n### React to Theme Changes\n\n```csharp\nApplication.Current!.RequestedThemeChanged += (s, e) =>\n{\n    AppTheme newTheme = e.RequestedTheme;\n    // Update UI or switch ResourceDictionaries\n};\n```\n\n## Combining Both Approaches\n\nUse `AppThemeBinding` with `DynamicResource` values for maximum flexibility:\n\n```xml\n<Label TextColor=\"{AppThemeBinding\n    Light={DynamicResource LightPrimary},\n    Dark={DynamicResource DarkPrimary}}\" />\n```\n\nOr react to system changes and swap full dictionaries:\n\n```csharp\nApplication.Current!.RequestedThemeChanged += (s, e) =>\n{\n    ApplyTheme(e.RequestedTheme == AppTheme.Dark\n        ? new DarkTheme()\n        : new LightTheme());\n};\n```\n\n## Saving and Restoring User Preference\n\nStore the user's choice with `Preferences` and apply it on startup:\n\n```csharp\n// Save choice\nPreferences.Set(\"AppTheme\", \"Dark\");\n\n// Restore on startup (in App constructor or CreateWindow)\nvar saved = Preferences.Get(\"AppTheme\", \"System\");\nApplication.Current!.UserAppTheme = saved switch\n{\n    \"Light\" => AppTheme.Light,\n    \"Dark\"  => AppTheme.Dark,\n    _       => AppTheme.Unspecified\n};\n```\n\n## Common Pitfalls\n\n### Android: ConfigChanges.UiMode is Required\n\n`MainActivity` **must** include `ConfigChanges.UiMode` or theme-change events will not fire and the activity restarts instead of handling the change gracefully:\n\n```csharp\n[Activity(Theme = \"@style/Maui.SplashTheme\",\n          MainLauncher = true,\n          ConfigurationChanges = ConfigChanges.ScreenSize\n                               | ConfigChanges.Orientation\n                               | ConfigChanges.UiMode  // ← Required for theme detection\n                               | ConfigChanges.ScreenLayout\n                               | ConfigChanges.SmallestScreenSize\n                               | ConfigChanges.Density)]\npublic class MainActivity : MauiAppCompatActivity { }\n```\n\nWithout `UiMode`, toggling dark mode in Android settings causes a full activity restart — losing navigation state and appearing as a crash.\n\n### DynamicResource vs StaticResource\n\nWhen using ResourceDictionary theme switching, you **must** use `DynamicResource`:\n\n```xml\n<!-- ✅ Updates when theme dictionary changes -->\n<Label TextColor=\"{DynamicResource PrimaryTextColor}\" />\n\n<!-- ❌ Frozen at first load — won't update on theme switch -->\n<Label TextColor=\"{StaticResource PrimaryTextColor}\" />\n```\n\n### Hardcoded Colors Break Theming\n\nAvoid inline color values on elements that should respect the theme:\n\n```xml\n<!-- ❌ Will not change with theme -->\n<Label TextColor=\"#333333\" />\n\n<!-- ✅ Theme-aware -->\n<Label TextColor=\"{DynamicResource PrimaryTextColor}\" />\n```\n\n### CSS Themes Cannot Be Swapped at Runtime\n\n.NET MAUI supports CSS styling, but CSS-based themes **cannot be swapped dynamically**. Use ResourceDictionary theming for runtime switching.\n\n### Theme Keys Must Match Across Dictionaries\n\nEvery `x:Key` used in one theme dictionary must exist in all other theme dictionaries. A missing key causes a silent fallback to the default value, leading to inconsistent appearance.\n\n## Platform Support\n\n| Platform       | Minimum Version |\n|----------------|-----------------|\n| iOS            | 13+             |\n| Android        | 10+ (API 29)    |\n| macOS Catalyst | 10.15+          |\n| Windows        | 10+             |\n\n## Quick Reference\n\n- **OS light/dark** → `AppThemeBinding` markup extension\n- **Theme colors in C#** → `SetAppThemeColor()`, `SetAppTheme<T>()`\n- **Read OS theme** → `Application.Current.RequestedTheme`\n- **Force theme** → `Application.Current.UserAppTheme = AppTheme.Dark`\n- **Theme changes** → `RequestedThemeChanged` event\n- **Custom switching** → Swap `ResourceDictionary` in `MergedDictionaries`\n- **Runtime bindings** → **`DynamicResource`** (not `StaticResource`)\n- **Persist choice** → `Preferences.Set` / `Preferences.Get`",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/maui-theming"
  ],
  "tags": [
    "dotnet-maui",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-maui/skills/maui-theming/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-maui/skills/maui-theming/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}