From uno-platform
Extends dotnet-livecharts2 for Uno Platform apps that need reliable in-app dark/light/system theme switching with LiveCharts2, shared theme palettes, central refresh of already-loaded charts, and rendered-pixel verification of chart text colors after theme changes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/uno-platform:uno-livecharts2-theme-switchingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use with `dotnet-livecharts2`, not instead of it.
Use with dotnet-livecharts2, not instead of it.
Use when:
Do NOT assume LiveCharts2 will follow Uno in-app theme switching automatically.
Why:
Application.Current.RequestedThemeIThemeServiceIThemeService updates app theme state but does not update Application.Current.RequestedThemeUse one app-owned theme source of truth plus one central chart refresh.
Do:
ChartPalette helper that resolves resource brushes/colors for a requested dark/light theme.ChartThemeConfig with:
Initialize() for global LiveCharts.Configure(...)Create(requestedTheme) for per-control reassignmentLiveCharts.DefaultSettings.GetTheme().RequestedThemechart.ChartTheme = ChartThemeConfig.Create(requestedTheme) when neededchart.CoreChart.ApplyTheme()Prefer this over:
Keep chart colors in the same light/dark resource system as the rest of the app.
Pattern:
Minimal shape:
static class ChartPalette
{
public static SKColor ThemeColor(string brushKey, bool isDark) { /* resolve Light/Dark brush */ }
public static SolidColorPaint ThemeSolid(string brushKey, bool isDark, byte? alpha = null) { /* ... */ }
public static SolidColorPaint ThemeStroke(string brushKey, bool isDark, float thickness = 2) { /* ... */ }
}
Keep semantic series tags/tokens separate from theme brushes:
Important:
Use a reusable theme factory instead of ad hoc per-chart paint mutation.
Required hookup:
ChartThemeConfig.Initialize() once during app startup, before charts renderIThemeService, make sure host setup includes .UseThemeSwitching()Minimal shape:
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Kernel.Sketches;
using LiveChartsCore.Measure;
using LiveChartsCore.SkiaSharpView.SKCharts;
using LiveChartsCore.Themes;
using LiveChartsCore.VisualStates;
static class ChartThemeConfig
{
public static void Initialize() =>
LiveCharts.Configure(config => config.HasTheme(Apply));
public static Theme Create(LvcThemeKind requestedTheme)
{
var theme = new Theme { RequestedTheme = requestedTheme };
Apply(theme);
return theme;
}
static void Apply(Theme theme) =>
theme
.OnInitialized(() =>
{
theme.Colors = ChartPalette.DefaultSeriesPalette(theme.IsDark);
theme.VirtualBackroundColor = ToLvcColor("ChartSurfaceBrush", theme.IsDark);
theme.TooltipTextPaint = ChartPalette.ThemeSolid("ChartTextPrimaryBrush", theme.IsDark);
theme.TooltipBackgroundPaint = ChartPalette.ThemeSolid("ChartTooltipBackgroundBrush", theme.IsDark);
theme.LegendTextPaint = ChartPalette.ThemeSolid("ChartTextPrimaryBrush", theme.IsDark);
})
.HasDefaultTooltip(() => new SKDefaultTooltip())
.HasDefaultLegend(() => new SKDefaultLegend())
.HasRuleForAxes(axis =>
{
axis.NamePaint = ChartPalette.ThemeSolid("ChartTextPrimaryBrush", theme.IsDark);
axis.LabelsPaint = ChartPalette.ThemeSolid("ChartTextSecondaryBrush", theme.IsDark);
axis.SeparatorsPaint = ChartPalette.ThemeStroke("ChartAxisSeparatorBrush", theme.IsDark, 1);
})
.HasRuleForAnySeries(series =>
{
if (series.ShowDataLabels)
series.DataLabelsPaint = ChartPalette.ThemeSolid("ChartTextPrimaryBrush", theme.IsDark);
_ = series.HasState("Hover",
[
(nameof(DrawnGeometry.Opacity), 0.8f)
]);
});
}
Notes:
HasTheme(...) directly for custom Uno theme controltheme.Colors from the same themed ChartSeries...Brush resources used elsewhere in the appPublish the effective chart theme from the same code path that performs the app theme switch.
Why:
RequestedTheme, ActualThemeChanged, or ThemeChanged may not be sufficient alone in UnoMinimal shape:
using LiveChartsCore.Themes;
static class LiveChartsThemeState
{
public static event EventHandler<LvcThemeKind>? Changed;
public static LvcThemeKind CurrentRequestedTheme { get; private set; } = LvcThemeKind.Light;
public static void Set(bool isDark) =>
Set(isDark ? LvcThemeKind.Dark : LvcThemeKind.Light);
public static void Set(LvcThemeKind requestedTheme)
{
if (CurrentRequestedTheme == requestedTheme)
return;
CurrentRequestedTheme = requestedTheme;
Changed?.Invoke(null, requestedTheme);
}
}
Publish from the theme toggle after SetThemeAsync(...) completes:
[RelayCommand]
async Task ToggleTheme()
{
_ = await themeService.SetThemeAsync(nextTheme);
LiveChartsThemeState.Set(themeService.IsDark);
}
If app has System, Light, Dark:
Also seed the initial chart theme state before the first toggle.
Minimal shape:
void OnLoaded(object sender, RoutedEventArgs e)
{
var themeService = this.GetThemeService();
LiveChartsThemeState.Set(themeService.IsDark);
// subscribe and refresh after this
}
If your app keeps a separate 3-state toggle index for System/Light/Dark, initialize that from themeService.Theme too, before handling toggle clicks.
Initialize the chart theme system once during app startup.
Minimal shape:
public partial class App : Application
{
public App()
{
ChartThemeConfig.Initialize();
InitializeComponent();
}
}
If using Uno host builder theme switching:
var builder = this.CreateBuilder(args)
.Configure(host => host
.UseThemeSwitching()
// other setup
);
The central refresh example assumes:
NavViewMainRegionMinimal shape:
<Page
x:Class="MyApp.Presentation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uen="using:Uno.Extensions.Navigation.UI">
<Grid uen:Region.Attached="True">
<NavigationView x:Name="NavView"
uen:Region.Attached="True">
<!-- shell header/menu -->
<Grid x:Name="MainRegion"
uen:Region.Attached="True"
uen:Region.Navigator="Visibility"/>
</NavigationView>
</Grid>
</Page>
Refresh charts from one root location in the live tree.
Recommended:
LoadedMinimal shape:
using LiveChartsCore.SkiaSharpView.WinUI;
using LiveChartsCore.Themes;
using Microsoft.UI.Xaml.Media;
public sealed partial class MainPage : Page
{
DependencyObject? activeRegionChild;
public MainPage()
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
void OnLoaded(object sender, RoutedEventArgs e)
{
var themeService = this.GetThemeService();
LiveChartsThemeState.Set(themeService.IsDark);
LiveChartsThemeState.Changed += OnChartThemeChanged;
MainRegion.LayoutUpdated += OnMainRegionLayoutUpdated;
RefreshChartThemes();
}
void OnUnloaded(object sender, RoutedEventArgs e)
{
LiveChartsThemeState.Changed -= OnChartThemeChanged;
MainRegion.LayoutUpdated -= OnMainRegionLayoutUpdated;
activeRegionChild = null;
}
void OnChartThemeChanged(object? sender, LvcThemeKind args) =>
RefreshChartThemes();
void OnMainRegionLayoutUpdated(object? sender, object args)
{
var currentRegionChild = VisualTreeHelper.GetChildrenCount(MainRegion) > 0
? VisualTreeHelper.GetChild(MainRegion, 0)
: null;
if (ReferenceEquals(currentRegionChild, activeRegionChild))
return;
activeRegionChild = currentRegionChild;
RefreshChartThemes();
}
void RefreshChartThemes()
{
var requestedTheme = LiveChartsThemeState.CurrentRequestedTheme;
LiveChartsCore.LiveCharts.DefaultSettings.GetTheme().RequestedTheme = requestedTheme;
RefreshChartThemes(NavView, requestedTheme);
}
static void RefreshChartThemes(DependencyObject root, LvcThemeKind requestedTheme)
{
if (root is CartesianChart cartesianChart)
ApplyTheme(cartesianChart, requestedTheme);
else if (root is PieChart pieChart)
ApplyTheme(pieChart, requestedTheme);
var childCount = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < childCount; i++)
RefreshChartThemes(VisualTreeHelper.GetChild(root, i), requestedTheme);
}
static void ApplyTheme(CartesianChart chart, LvcThemeKind requestedTheme)
{
if (chart.ChartTheme?.RequestedTheme != requestedTheme)
chart.ChartTheme = ChartThemeConfig.Create(requestedTheme);
chart.CoreChart.ApplyTheme();
}
static void ApplyTheme(PieChart chart, LvcThemeKind requestedTheme)
{
if (chart.ChartTheme?.RequestedTheme != requestedTheme)
chart.ChartTheme = ChartThemeConfig.Create(requestedTheme);
chart.CoreChart.ApplyTheme();
}
}
Root selection rules:
Page instance if its content is elsewhereNavigationView, walk that root directly instead of an unnamed placeholder objectRequired hookups in this pattern:
Loaded / UnloadedOnLoaded subscribes LiveChartsThemeState.Changed and region/layout change signalOnLoaded seeds LiveChartsThemeState from the current effective theme before refreshOnUnloaded unsubscribes themChartThemeConfig.Initialize()LiveChartsThemeState.Set(...)It fixes both cases:
ChartThemeConfig.Create(...)ApplyTheme()It also avoids coupling to LiveCharts private listeners.
Avoid these unless forced by a framework constraint:
Application.Current.RequestedTheme to prove Uno theme switch propagationChartTheme once at load time and expecting it to survive later theme switchesActualThemeChanged sync logicRequired verification: rendered chart text pixels, not state variables.
Test matrix:
System/Light/Dark, exercise the full cycle needed to reach the intended effective themeScreenshot guidance:
What to inspect:
Reliable verification methods:
Unreliable verification methods to avoid:
themeService.IsDarkApplication.Current.RequestedThemeActualThemeSymptom: pages first visited after switch are correct, visited pages stale.
Meaning:
Symptom: chart text flips correctly for a moment, then reverts to the opposite theme.
Meaning:
LiveCharts.DefaultSettings.GetTheme().RequestedThemeSymptom: current page updates, but previously visited cached pages revert when revisited.
Meaning:
For Uno in-app theme switching with shared palettes:
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub vincenth-net/dotnet-agentic-engineering --plugin uno-platform