How Clash Nyanpasu Implements Material You Theming and Dynamic Color Systems
Clash Nyanpasu generates a complete Material Design 3 theme from a user-defined seed color using Google's Material Color Utilities, then distributes the dynamic palette throughout the application via React context and CSS custom properties.
Clash Nyanpasu is a modern GUI proxy client built with React and Tauri that leverages Google's Material You (Material Design 3) specification for its theming system. The implementation centers on dynamic color generation, where a single seed color propagates into a full tonal palette that adapts to both user preferences and operating system theme changes.
Core Architecture of Material You Theming
The theming system is orchestrated through a custom React hook that bridges user settings, Material Design algorithms, and the component tree.
The use-custom-theme.tsx Hook
Located at frontend/nyanpasu/src/components/layout/use-custom-theme.tsx, this hook serves as the central nervous system for theme generation. It performs five critical functions:
- Reads the user-defined seed color from the
theme_colorsetting, defaulting to#1867c0if undefined - Invokes the Material You generator via
createMDYThemefrom the workspace package@nyanpasu/ui - Provides the theme to the React tree through MUI's
<ThemeProvider> - Synchronizes OS-level color schemes using Tauri's
appWindow.theme()API and theuseColorSchemehook - Updates CSS custom properties on the
<html>element (e.g.,--background-color,--primary-main) for non-MUI components
The hook uses useMemo to recompute the theme only when the seed color or color mode changes, ensuring efficient updates without full page reloads.
Theme Generation with createMDYTheme
The createMDYTheme function (implemented in the @nyanpasu/ui workspace package) acts as the bridge between Google's color science and MUI's theming system:
// Conceptual implementation based on source analysis
import { CorePalette, argbFromHex } from '@material/material-color-utilities';
const createMDYTheme = (seedColor: string, fontFamily: string) => {
// Convert hex to ARGB and generate tonal palettes
const seedArgb = argbFromHex(seedColor);
const corePalette = CorePalette.of(seedArgb);
// Map Material You tonal palettes to MUI palette structure
return createMuiTheme({
palette: {
primary: generateTonalPalette(corePalette.a1),
secondary: generateTonalPalette(corePalette.a2),
tertiary: generateTonalPalette(corePalette.a3),
error: generateTonalPalette(corePalette.error),
},
typography: { fontFamily }
});
};
This function relies on @material/material-color-utilities (declared in frontend/ui/package.json) to execute the Material You algorithm, which extracts hue, chroma, and tone values to generate harmonious color families.
Dynamic Color System Implementation
Seed Color and Tonal Palettes
The dynamic color system originates from a single seed color stored in the application's settings. When the user selects a new accent color:
- The setting updates via the
useSetting('theme_color')hook - The new hex value flows into
createMDYTheme @material/material-color-utilitiescalculates tonal palettes for primary, secondary, tertiary, and error containers- These palettes map to MUI's
palette.primary,palette.secondary, etc.
Each tonal palette contains 13 tones (from 0% to 100% luminance), ensuring accessible contrast ratios across light and dark modes.
CSS Custom Properties Synchronization
To support components outside MUI's theme context (such as Tailwind-styled elements), the system synchronizes CSS custom properties:
// From use-custom-theme.tsx
const applyRootStyleVar = (mode: 'light' | 'dark', theme: Theme) => {
const root = document.documentElement;
root.style.setProperty('--background-color', theme.palette.background.default);
root.style.setProperty('--primary-main', theme.palette.primary.main);
root.style.setProperty('--primary-contrast', theme.palette.primary.contrastText);
// ... additional mappings
};
This dual-delivery system (React context + CSS variables) ensures consistent theming across the entire application stack.
OS-Level Theme Synchronization
Tauri Integration for System Theme Detection
The application respects the operating system's color scheme preference through Tauri's window API:
// Located in scheme-provider.tsx and use-custom-theme.tsx
import { appWindow } from '@tauri-apps/api/webviewWindow';
// Initialize theme from OS
const systemTheme = await appWindow.theme(); // returns 'light' | 'dark'
// Listen for changes
const unlisten = await appWindow.onThemeChanged((event) => {
setMode(event.payload); // Update Jotai atom and MUI scheme
});
This integration, combined with the scheme-provider.tsx component, ensures the application transitions seamlessly when the user toggles system dark mode.
Practical Implementation Examples
Accessing Theme Values in Components
import { Card, CardContent, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
export const InfoCard = () => {
const theme = useTheme(); // <- receives Material You colours
return (
<Card sx={{ backgroundColor: theme.palette.primary.main }}>
<CardContent>
<Typography color="primary.contrastText">
Material You themed card
</Typography>
</CardContent>
</Card>
);
};
Updating the Seed Color Programmatically
import { useSetting } from '@nyanpasu/interface';
import { Slider } from '@mui/material';
import { useState } from 'react';
export const ThemeColorPicker = () => {
const { value: current, setValue } = useSetting('theme_color');
const [hex, setHex] = useState(current ?? '#1867c0');
const handleChange = (_, newHex: string) => {
setHex(newHex);
setValue(newHex); // triggers re‑generation of the MDY theme
};
return <Slider value={hex} onChange={handleChange} />;
};
Synchronizing with System Theme Changes
import { useEffect } from 'react';
import { appWindow } from '@tauri-apps/api/webviewWindow';
import { useSetAtom } from 'jotai';
import { themeMode as themeModeAtom } from '@/store';
export const SyncSystemTheme = () => {
const setMode = useSetAtom(themeModeAtom);
useEffect(() => {
appWindow.theme().then(m => m && setMode(m)); // initialise
const unlisten = appWindow.onThemeChanged(e => setMode(e.payload));
return () => unlisten.then(fn => fn());
}, []);
return null;
};
Summary
- Central Hook: The
use-custom-theme.tsxhook infrontend/nyanpasu/src/components/layout/orchestrates the entire theming system by reading user settings and generating MUI themes. - Material You Integration: The
@nyanpasu/uiworkspace package implementscreateMDYTheme, which leverages@material/material-color-utilitiesto generate tonal palettes from a single seed color. - Dynamic Updates: The system uses
useMemoto recompute themes when thetheme_colorsetting changes, applying updates via React context and CSS custom properties without page reloads. - OS Synchronization: Tauri's
appWindow.theme()API andonThemeChangedevent listener ensure the application respects and reacts to system-level light/dark mode changes. - Dual Delivery: Themes are distributed through both MUI's
<ThemeProvider>and synchronized CSS variables (--primary-main, etc.), ensuring consistent styling across MUI components and Tailwind-styled elements.
Frequently Asked Questions
How does Clash Nyanpasu generate a full color palette from a single hex code?
Clash Nyanpasu uses Google's @material/material-color-utilities library to execute the Material You algorithm. When a user selects a seed color (e.g., #1867c0), the createMDYTheme function converts this to ARGB format and passes it to CorePalette.of(), which mathematically derives hue, chroma, and tone values to generate 13-tone palettes for primary, secondary, tertiary, and error colors.
What happens when the user changes the theme color while the app is running?
The change triggers an immediate update through the use-custom-theme.tsx hook. Because the theme generation is wrapped in useMemo with dependencies on theme_color and the current color mode, React recomputes the MUI theme object instantly. The hook then updates the <ThemeProvider> context and synchronizes CSS custom properties on the <html> element, causing the entire UI to repaint with the new palette without requiring a page reload.
How does the application handle system dark mode or light mode changes?
The application listens for OS-level theme changes through Tauri's appWindow.onThemeChanged event listener, implemented in scheme-provider.tsx and the custom theme hook. When the system theme changes, the event payload updates a Jotai atom (themeModeAtom) and MUI's useColorScheme hook, which triggers the theme generator to switch between light and dark palette variants while maintaining the same seed color and tonal relationships.
Can non-MUI components access the Material You color values?
Yes. While MUI components consume the theme through React context, the system exposes the same color values as CSS custom properties on the root <html> element. The applyRootStyleVar function in use-custom-theme.tsx synchronizes variables such as --primary-main, --background-color, and --primary-contrast whenever the theme updates. This allows Tailwind CSS and custom styled components to reference these values using var(--primary-main) and remain visually consistent with the Material You palette.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →