feat: Add Blazor theme patterns skill documentation and improve dark mode drawer text contrast in MainLayout.
This commit is contained in:
137
.agent/skills/blazor-theme-patterns/SKILL.md
Normal file
137
.agent/skills/blazor-theme-patterns/SKILL.md
Normal file
@@ -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
|
||||
<script>
|
||||
window.setTheme = (theme) => {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Step 3: call from Blazor Layout
|
||||
In `MainLayout.razor`, inject `IJSRuntime` and call the helper when toggling the theme.
|
||||
|
||||
```razor
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<MudThemeProvider @bind-IsDarkMode="_isDarkMode" Theme="_theme" />
|
||||
|
||||
@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
|
||||
<MudAppBar Color="Color.Default" Elevation="0" ... />
|
||||
```
|
||||
|
||||
### 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?
|
||||
@@ -12,17 +12,20 @@
|
||||
<MudSpacer />
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<MudStack Row="true" Spacing="4" Class="d-none d-md-flex align-center mr-8">
|
||||
<MudLink Href="/" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Home</MudLink>
|
||||
<MudLink Href="/features" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Features</MudLink>
|
||||
<MudLink Href="/enterprise" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Enterprise</MudLink>
|
||||
<MudLink Href="/pricing" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Pricing</MudLink>
|
||||
</MudStack>
|
||||
<!-- Desktop Navigation & Actions (Hidden on Mobile) -->
|
||||
<div class="d-none d-md-flex align-center gap-4">
|
||||
<MudStack Row="true" Spacing="4" Class="mr-4">
|
||||
<MudLink Href="/" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Home</MudLink>
|
||||
<MudLink Href="/features" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Features</MudLink>
|
||||
<MudLink Href="/enterprise" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Enterprise</MudLink>
|
||||
<MudLink Href="/pricing" Color="Color.Inherit" Underline="Underline.None" Class="mud-nav-link px-3 py-2">Pricing</MudLink>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Inherit" Class="rounded-lg">Sign in</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="rounded-lg">Download</MudButton>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Inherit" Class="rounded-lg">Sign in</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="rounded-lg">Download</MudButton>
|
||||
</MudStack>
|
||||
</div>
|
||||
<MudIconButton Icon="@(_isDarkMode ? Icons.Material.Rounded.LightMode : Icons.Material.Rounded.DarkMode)"
|
||||
Color="Color.Inherit"
|
||||
Class="ml-2"
|
||||
@@ -72,8 +75,8 @@
|
||||
AppbarText = "#fafafa",
|
||||
Background = "#09090b",
|
||||
Surface = "#18181b", // Zinc-900
|
||||
DrawerBackground = "#09090b",
|
||||
DrawerText = "#a1a1aa", // Zinc-400
|
||||
DrawerBackground = "#09090b", // Zinc-950 (Match Body Background)
|
||||
DrawerText = "#fafafa", // Zinc-50 (Ligher text for better contrast)
|
||||
TextPrimary = "#fafafa",
|
||||
TextSecondary = "#a1a1aa",
|
||||
ActionDefault = "#a1a1aa",
|
||||
|
||||
Reference in New Issue
Block a user