From b022eb10716a2395b801c3f66d47ad6dddee050f Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Mon, 19 Jan 2026 10:32:11 +0700 Subject: [PATCH] feat: Add Blazor theme patterns skill documentation and improve dark mode drawer text contrast in MainLayout. --- .agent/skills/blazor-theme-patterns/SKILL.md | 137 ++++++++++++++++++ .../Layout/MainLayout.razor | 27 ++-- 2 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 .agent/skills/blazor-theme-patterns/SKILL.md diff --git a/.agent/skills/blazor-theme-patterns/SKILL.md b/.agent/skills/blazor-theme-patterns/SKILL.md new file mode 100644 index 00000000..73214e3f --- /dev/null +++ b/.agent/skills/blazor-theme-patterns/SKILL.md @@ -0,0 +1,137 @@ +--- +name: blazor-theme-patterns +description: Best practices for implementing Light/Dark themes in Blazor (MudBlazor) applications, ensuring synchronization between C# and CSS. +--- + +# Blazor Theme Patterns + +This skill documents the patterns and best practices for implementing robust Light/Dark theming in Blazor applications, specifically when using **MudBlazor** alongside **Custom CSS**. + +## 1. The Hybrid Theming Problem + +Blazor apps often have two sources of truth for styling: +1. **MudBlazor Theme (`MudTheme` in C#)**: Controls MudBlazor components (AppBar, Drawer, Buttons). +2. **CSS Variables (`app.css`)**: Controls native HTML elements (`body`, `h1`, custom divs) and global layout. + +**Risk**: If these two are not synchronized, you get "partial themes" (e.g., Dark Header but Light Body). + +## 2. Solution: synchronization Pattern + +Always synchronize the Blazor theme state with the DOM via JavaScript Interop. + +### Step 1: Define CSS Variables with Data Attribute +In your `app.css`, define variables using the `[data-theme="dark"]` selector. + +```css +:root { + /* Light Mode Defaults */ + --bg-primary: #ffffff; + --text-primary: #18181b; +} + +[data-theme="dark"], :root[data-theme="dark"] { + /* Dark Mode Overrides */ + --bg-primary: #09090b; + --text-primary: #fafafa; +} + +/* Enforce Body Background */ +body { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + transition: background-color 0.3s ease; +} +``` + +### Step 2: Add JS Helper +In `index.html` (before `MudBlazor.min.js`), add a lightweight helper to switch attributes. + +```html + +``` + +### Step 3: call from Blazor Layout +In `MainLayout.razor`, inject `IJSRuntime` and call the helper when toggling the theme. + +```razor +@inject IJSRuntime JSRuntime + + + +@code { + private bool _isDarkMode; + private MudTheme _theme = new(); + + private async Task ToggleDarkMode() + { + _isDarkMode = !_isDarkMode; + // Sync with CSS + await JSRuntime.InvokeVoidAsync("setTheme", _isDarkMode ? "dark" : "light"); + } +} +``` + +## 3. Common Pitfalls & Solutions + +### A. "Invisible" Text in Dark Mode (White on White) +**Issue**: MudBlazor's `Primary` color might be White in Dark Mode (e.g., `#fafafa`). If the `PrimaryContrastText` is not set, it might default to White, making text invisible on a Primary Filled Button. +**Fix**: Explicitly set `PrimaryContrastText` in your `PaletteDark`. + +```csharp +PaletteDark = new PaletteDark() +{ + Primary = "#fafafa", + PrimaryContrastText = "#18181b", // Force Black text on White Primary button + // ... +} +``` + +**CSS Hardening**: +If C# palette fails, force it locally in CSS: +```css +[data-theme="dark"] .mud-button-filled-primary { + color: #18181b !important; +} +``` + +### B. AppBar Color Mismatch +**Issue**: `MudAppBar` defaults to `Color.Primary` (often black/dark). In Light Mode, if you want a white AppBar, `Color.Primary` might conflict. +**Fix**: Use `Color="Color.Default"` and let the Theme Palette handle the background color (`AppbarBackground`). + +```razor + +``` + +### C. Development Workflow (Hot Reload) +**Recommendation**: Use `dotnet watch` during development to instantly see CSS and Razor changes without restarting. + +```bash +dotnet watch --project src/WebClientBase.Client +``` + +**Note**: +* **CSS Changes**: Applied instantly. +* **Razor Markup**: Applied instantly. +* **C# Logic (`@code`)**: usually applied instantly, but complex logic changes (like modifying `OnInitialized`) may require a manual restart (Ctrl+R). +* **Full Rebuild**: Required if you add constant fields or change method signatures that Hot Reload cannot handle. + +## 4. Verification Checklist + +When implementing themes, verify these points using the Browser Tool across multiple devices: + +### A. Core Theme Checks +1. **Body Background**: Does it change hex codes when toggled? +2. **Primary Button Text**: Is it readable (Contrast Ratio) in *both* Light and Dark modes? +3. **Headings**: Do standard `h1`-`h6` tags invert color correctly? + +### B. Responsive Checks (Crucial) +1. **Desktop (> 960px)**: Check standard navigation bar and layout. +2. **Tablet (768px - 960px)**: Check if layout adapts (e.g. grid changes). +3. **Mobile (< 768px)**: + - **Hamburger Menu**: Is the icon visible in both themes? + - **Drawer/Sidebar**: Does the drawer background match the theme (especially in Dark Mode)? + - **Mobile Navigation**: are links readable? diff --git a/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor b/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor index cf00e480..834bc0d4 100644 --- a/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor +++ b/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor @@ -12,17 +12,20 @@ - - Home - Features - Enterprise - Pricing - + +
+ + Home + Features + Enterprise + Pricing + - - Sign in - Download - + + Sign in + Download + +