Skip to content

Apply required improvements to theme system of BlazorUI (#12303)#12304

Open
msynk wants to merge 2 commits intobitfoundation:developfrom
msynk:12303-blazorui-theme-improvements
Open

Apply required improvements to theme system of BlazorUI (#12303)#12304
msynk wants to merge 2 commits intobitfoundation:developfrom
msynk:12303-blazorui-theme-improvements

Conversation

@msynk
Copy link
Copy Markdown
Member

@msynk msynk commented May 4, 2026

closes #12303

Summary by CodeRabbit

  • New Features

    • Added dark and light color variants for primary, secondary, and tertiary theme colors with hover and active states.
    • Added eight new shadow size tokens (Callout2, Sm, Nm, Md, Lg, Xl, Xxl, Inner) for enhanced shadow customization.
    • Introduced theme presets (Light, Dark, Fluent, System) for easier theme selection.
    • Added theme serialization support for persisting and loading custom themes.
    • Added SSR helper to prevent theme flash on first page load.
    • Introduced new theme utilities for merging and converting themes to CSS variables.
  • Documentation

    • Expanded theming documentation with new APIs, examples, and SSR integration guidance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fb362cbd-c3fa-459e-93a3-dbd31a973b43

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The PR enhances the BlazorUI theme system by expanding color and shadow variant properties, introducing utility classes for theme serialization, color role derivation, and SSR support, and refactoring the CSS variable mapper to emit additional dark/light shade variants. CSS variable generation now delegates through a new public utility layer.

Changes

Theme System Improvements

Layer / File(s) Summary
Data Shape Expansion
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeBoxShadows.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeColors.cs
BitThemeBoxShadows adds 8 nullable properties (Callout2, Sm, Nm, Md, Lg, Xl, Xxl, Inner). BitThemeGeneralColorVariants adds 18 nullable Dark* and Light* variants for Primary, Secondary, Tertiary (including Hover/Active states).
Color Derivation & Utilities
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeColorDerivation.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs
New BitThemeColorDerivation derives semantic color variants (dark, light, hover, active, text) from a main hex value via HSV scaling. BitThemeUtilities provides public ToCssVariables() and Merge() wrappers. BitThemeSerialization adds JSON serialization with camelCase and null-omission configuration.
SSR & Preset Support
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemePresets.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs
New BitThemePresets class defines theme preset string constants (Light, Dark, Fluent, etc.). BitThemeSsr provides inline script body for early theme restoration from localStorage and system mode detection.
Mapper & CSS Variable Expansion
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeMapper.cs
MapToCssVariables emits additional CSS variable tokens for foreground, background, and border dark/light shade variants (*-dark, *-dark-hover, *-dark-active, *-light, *-light-hover, *-light-active) and new box-shadow variants. Merge extends to handle new color variant and shadow properties.
Integration & API Cleanup
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeManager.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs, src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeJsExtensions.cs
BitThemeManager calls BitThemeUtilities.ToCssVariables instead of BitThemeMapper.MapToCssVariables and adds XML documentation. BitThemeProvider consistently uses merged theme for CSS variable generation and cascaded value. BitThemeJsExtensions accepts IReadOnlyDictionary for better type safety.
Testing & Documentation
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Utils/Theme/BitThemeMapperContractTests.cs, src/BlazorUI/Tests/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj, src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/ThemingPage.razor
New contract tests verify CSS variable token coverage, serialization round-trip, and merge behavior. Test project links theme-variables.scss for validation. Demo documentation expands to cover presets, utilities, SSR, serialization, derivation, and precedence rules with code examples.

Sequence Diagram

sequenceDiagram
    participant User as User/App
    participant Provider as BitThemeProvider
    participant Manager as BitThemeManager
    participant Utilities as BitThemeUtilities
    participant Mapper as BitThemeMapper
    participant JS as JS Interop

    User->>Provider: Set Theme & ParentTheme
    Provider->>Utilities: Merge(Theme, ParentTheme)
    Utilities->>Mapper: Merge(theme1, theme2)
    Mapper-->>Utilities: merged BitTheme
    Utilities-->>Provider: merged BitTheme

    Provider->>Utilities: ToCssVariables(merged)
    Utilities->>Mapper: MapToCssVariables(theme)
    Mapper-->>Utilities: IReadOnlyDictionary<string, string>
    Utilities-->>Provider: CSS variables dict

    Provider->>Manager: ApplyBitThemeAsync(merged)
    Manager->>Utilities: ToCssVariables(merged)
    Utilities->>Mapper: MapToCssVariables(theme)
    Mapper-->>Utilities: CSS variables
    Utilities-->>Manager: CSS variables dict
    Manager->>JS: BitThemeApplyBitTheme(variables, element)
    JS->>JS: Set CSS custom properties on element
    JS-->>Manager: Applied
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A theme system blooms with shadows deep,
Dark and light variants dance and leap,
Colors derive from HSV's art,
SSR scripts prevent the flash-of-dark,
Presets and utils make theming complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: applying improvements to the BlazorUI theme system as specified in issue #12303.
Linked Issues check ✅ Passed All coding objectives from issue #12303 are addressed: merged theme issues in BitThemeProvider, missing color variants and box shadows added, BitThemeMapper improvements, and new utilities plus unit tests included.
Out of Scope Changes check ✅ Passed All changes are directly related to theme system improvements specified in #12303; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the BlazorUI theme system by fixing theme merging behavior in BitThemeProvider, filling missing theme tokens (color variants + box shadows), and adding public utilities to map/merge/serialize themes—plus a small SSR helper and accompanying documentation/tests.

Changes:

  • Fix BitThemeProvider to emit CSS variables and cascade a merged theme (child overrides + parent fallback), rather than only the child theme.
  • Expand theme surface area (additional foreground/background/border variants and box-shadow tokens) and ensure they’re mapped/merged in BitThemeMapper.
  • Add public helpers (BitThemeUtilities, BitThemePresets, BitThemeSerialization, BitThemeColorDerivation, BitThemeSsr), update demo docs, and add contract-style unit tests.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Utils/Theme/BitThemeMapperContractTests.cs Adds mapper/merge/serialization contract tests, including a SCSS-driven “all referenced tokens are mapped” check.
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj Copies theme-variables.scss into test output for the contract test.
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/ThemingPage.razor Documents precedence (preset vs inline vars) and introduces new utilities/SSR helper in the demo page.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs New public wrapper for mapping a BitTheme to CSS variables and merging themes.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs New SSR “early head script” helper for restoring/resolving bit-theme before CSS loads.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs New JSON serialize/deserialize helpers for BitTheme.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs Fixes merged-theme handling so inline CSS vars and cascaded value align.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemePresets.cs Adds constants for well-known bit-theme preset names.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeMapper.cs Adds missing CSS var mappings + merge support (general color variants and box shadows) and removes a duplicate typography block.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeManager.cs Improves docs and routes Apply→CSS-vars mapping via BitThemeUtilities.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeJsExtensions.cs Updates JS interop signature to accept IReadOnlyDictionary for theme variables.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeColorDerivation.cs Adds helper to derive variant steps from a single main color without overwriting explicit values.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeColors.cs Expands BitThemeGeneralColorVariants with dark/light steps for primary/secondary/tertiary.
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeBoxShadows.cs Adds missing box-shadow fields (callout2, sizes, inner) to the theme model.

Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs
Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeManager.cs
Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs
Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs
Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs (1)

11-16: ⚡ Quick win

Consider adding PropertyNameCaseInsensitive = true for robustness

Without it, deserialization uses case-sensitive matching against the camelCase-transformed property names. JSON produced by a different serializer (e.g. System.Text.Json default / Newtonsoft.Json default, which both emit PascalCase) will silently drop every field, returning a default BitTheme. The docs describe the use case as "sharing brand tokens", which implies possible interop with external producers.

🛡️ Proposed fix
 private static readonly JsonSerializerOptions Options = new()
 {
     PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
     WriteIndented = true,
     DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+    PropertyNameCaseInsensitive = true,
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs` around lines
11 - 16, The JsonSerializerOptions defined in BitThemeSerialization (the static
readonly Options) is currently case-sensitive and can drop properties when
deserializing JSON with PascalCase names; update the Options object to set
PropertyNameCaseInsensitive = true so deserialization will match property names
regardless of casing (ensuring BitTheme deserialization works with external
producers using PascalCase).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeColorDerivation.cs`:
- Around line 24-26: The light variant steps collapse because ScaleV uses
multiplicative scaling (ScaleV(v, 1.08/1.12/1.16)) causing high-v colors to
clamp to white; update BitThemeColorDerivation so the light variants
(variants.Light, variants.LightHover, variants.LightActive) use an additive
brightness offset instead of multiplicative scaling (e.g., replace ScaleV(v,
factor) calls with a method that returns ToHex(h, s, ClampV(v + delta)) or add
an AddV helper to compute v + fixedDelta and clamp to 1.0); keep referencing
ToHex, ScaleV (or new AddV/ClampV) and the variants.* properties so the steps
remain visually distinct for bright colors.

In `@src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs`:
- Around line 12-18: The SSR inline script constant InlineHeadScriptBody in
BitThemeSsr.cs doesn't honor the bit-theme-system attribute and so falls back to
light/dark before checking system preference; update the string in
InlineHeadScriptBody to first check if the document element has attribute
'bit-theme-system' (and if so resolve cur via matchMedia to dk/lt), then
fallback to persisted localStorage or bit-theme/bit-theme-default, keeping the
existing lt/dk retrieval and final r.setAttribute('bit-theme', cur) behavior so
server-side first paint matches the client helper logic.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/ThemingPage.razor`:
- Around line 281-284: The CodeBox example contains an escaped-at sequence that
produces invalid Razor: remove the extra escape before BitThemeSsr so the
expression reads as a normal cast; update the snippet in ThemingPage.razor (the
CodeBox showing @((MarkupString)...)) to use BitThemeSsr.InlineHeadScript
without the extra '@' escape so it renders as
@((MarkupString)BitThemeSsr.InlineHeadScript), not
@((MarkupString)@BitThemeSsr.InlineHeadScript).

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs`:
- Around line 11-16: The JsonSerializerOptions defined in BitThemeSerialization
(the static readonly Options) is currently case-sensitive and can drop
properties when deserializing JSON with PascalCase names; update the Options
object to set PropertyNameCaseInsensitive = true so deserialization will match
property names regardless of casing (ensuring BitTheme deserialization works
with external producers using PascalCase).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b36e58d0-269b-4e56-914d-e06688b68b65

📥 Commits

Reviewing files that changed from the base of the PR and between 54a323c and 0006c00.

📒 Files selected for processing (14)
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeBoxShadows.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitTheme/BitThemeColors.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeColorDerivation.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeJsExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeManager.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeMapper.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemePresets.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeProvider.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSerialization.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs
  • src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeUtilities.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/ThemingPage.razor
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Utils/Theme/BitThemeMapperContractTests.cs

Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeColorDerivation.cs Outdated
Comment thread src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitThemeSsr.cs
@msynk
Copy link
Copy Markdown
Member Author

msynk commented May 5, 2026

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Tip

For best results, initiate chat on the files or code changes.

@msynk: Sure, I'll re-review the PR now!

[review]

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

public const string InlineHeadScriptBody =
"(function(){var r=document.documentElement,k='bit-current-theme',cur;" +
"if(r.hasAttribute('bit-theme-persist')){cur=localStorage.getItem(k);}" +
"cur=cur||r.getAttribute('bit-theme')||r.getAttribute('bit-theme-default')||'light';" +
/// </summary>
public const string InlineHeadScriptBody =
"(function(){var r=document.documentElement,k='bit-current-theme',cur;" +
"if(r.hasAttribute('bit-theme-persist')){cur=localStorage.getItem(k);}" +
Comment on lines +36 to 42
/// <summary>Applies <paramref name="bitTheme"/> as CSS custom properties on <paramref name="element"/> (default: body), overriding stylesheet tokens for that subtree.</summary>
public async ValueTask ApplyBitThemeAsync(BitTheme bitTheme, ElementReference? element = null)
{
if (bitTheme is null) return;

await _js.BitThemeApplyBitTheme(BitThemeMapper.MapToCssVariables(bitTheme), element);
await _js.BitThemeApplyBitTheme(BitThemeUtilities.ToCssVariables(bitTheme), element);
}
Comment on lines +9 to +15
public static IReadOnlyDictionary<string, string> ToCssVariables(BitTheme bitTheme)
{
return BitThemeMapper.MapToCssVariables(bitTheme ?? new BitTheme());
}

/// <summary>Merges two themes: <paramref name="overrides"/> wins; missing values fall back to <paramref name="baseline"/>.</summary>
public static BitTheme Merge(BitTheme overrides, BitTheme baseline)
Comment on lines +13 to +33
private static readonly JsonSerializerOptions Options = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

public static string Serialize(BitTheme theme)
{
var node = JsonSerializer.SerializeToNode(theme ?? new BitTheme(), Options);
if (node is JsonObject root)
PruneEmptyObjects(root);
return node?.ToJsonString(Options) ?? "{}";
}

public static BitTheme Deserialize(string json)
{
return string.IsNullOrWhiteSpace(json)
? new BitTheme()
: (JsonSerializer.Deserialize<BitTheme>(json, Options) ?? new BitTheme());
}
public static class BitThemeColorDerivation
{
/// <summary>Fills unset <see cref="BitThemeColorVariants"/> fields from <paramref name="mainHex"/>.</summary>
public static void FillColorRoleFromMain(BitThemeColorVariants variants, string mainHex)
@@ -0,0 +1,221 @@
using System;
using System;
{
var v = new BitThemeColorVariants();
BitThemeColorDerivation.FillColorRoleFromMain(v, "not-a-color");
// The catch block silences errors; variants remain null.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The Theme system of the BlazorUI needs some improvements

5 participants