Theme Building
Design custom themes with the visual builder and apply them across all BPT components.
How Theming Works
Blazor Power Tools uses CSS custom properties (CSS variables) for theming. Every BPT component reads its colors, spacing, border radii, and other visual properties from a set of CSS variables. By changing these variables, you change the look of every component at once — no need to override individual component styles.
There are three ways to work with themes:
- Visual Theme Builder — A drag-and-drop GUI that generates theme files
- Manual CSS — Write CSS custom property overrides directly
- BptThemeLoader — Load and switch themes at runtime from CSS, JSON, or SCSS-ZIP bundles
Using the Visual Theme Builder
The BptThemeBuilder component is a built-in visual editor. It lets you pick colors, adjust spacing, set border radii, and preview changes in real time. When you're happy with the result, export the theme as a CSS file or JSON object.
Add the Theme Builder to a Page
<BptThemeBuilder OnThemeExported="HandleExport" />
@code {
private void HandleExport(string themeCss)
{
// Save to file, database, or apply directly
Console.WriteLine(themeCss);
}
}Adjust Colors and Properties
The builder displays live previews of components as you adjust:
- Primary, secondary, and accent colors
- Background and surface colors
- Text colors (primary, secondary, muted)
- Border radius (sharp, rounded, pill)
- Font family and sizing
- Shadow intensity
Export and Apply
Click "Export" to generate either:
- CSS file — Drop into your
wwwrootand link inApp.razor - JSON file — Load at runtime with
BptThemeLoader
Manual CSS Theming
Override BPT's default CSS custom properties in your own stylesheet:
/* my-theme.css */
:root {
/* Primary brand colors */
--bpt-color-primary: #1565C0;
--bpt-color-primary-light: #42A5F5;
--bpt-color-primary-dark: #0D47A1;
/* Secondary / accent */
--bpt-color-secondary: #FF6F00;
--bpt-color-accent: #00BFA5;
/* Backgrounds */
--bpt-bg-surface: #FFFFFF;
--bpt-bg-elevated: #F5F5F5;
--bpt-bg-overlay: rgba(0, 0, 0, 0.5);
/* Text */
--bpt-text-primary: #212121;
--bpt-text-secondary: #757575;
--bpt-text-on-primary: #FFFFFF;
/* Shape */
--bpt-border-radius: 8px;
--bpt-border-radius-sm: 4px;
--bpt-border-radius-lg: 16px;
/* Shadows */
--bpt-shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
--bpt-shadow-md: 0 4px 12px rgba(0,0,0,0.15);
--bpt-shadow-lg: 0 8px 24px rgba(0,0,0,0.2);
/* Typography */
--bpt-font-family: 'Inter', 'Segoe UI', sans-serif;
--bpt-font-size-base: 0.9375rem;
}Include the stylesheet in your App.razor after the BPT default styles:
<link href="_content/BlazorPowerTools/css/bpt.css" rel="stylesheet" />
<link href="css/my-theme.css" rel="stylesheet" />
A static <link> is the simplest option, but it locks the theme at page
load. For anything more dynamic — switching themes at runtime, applying a
JSON or SCSS-ZIP bundle exported from BptThemeBuilder,
or loading per-tenant themes — use the BptThemeLoader component.
Loading themes at runtime
BptThemeLoader applies a theme by injecting CSS variables onto
:root at runtime. It accepts three formats — CSS,
JSON, or SCSS-ZIP — from three sources:
URL, byte array, or base64 string.
JSON is the recommended format because it preserves component-specific overrides that
plain CSS cannot round-trip.
Headless mode — one theme, no UI
Drop the component anywhere in your tree and point it at a theme source:
<!-- From a URL (CSS, JSON, or .zip SCSS bundle) -->
<BptThemeLoader Url="/themes/ocean.json" />
<!-- From raw bytes (e.g. a user-uploaded file) -->
<BptThemeLoader Data="@_themeBytes" />
<!-- From a base64 string (e.g. stored in a database) -->
<BptThemeLoader Base64Data="@_themeBase64" />Built-in dropdown picker
Pass a dictionary of themes and BptThemeLoader renders a dropdown that switches between them — no extra wiring needed.
<BptThemeLoader Themes="@_themes"
@bind-SelectedTheme="_currentTheme"
Placeholder="Choose a theme" />
@code {
private string _currentTheme = "/themes/default.json";
private Dictionary<string, string> _themes = new()
{
["/themes/default.json"] = "Default",
["/themes/dark.json"] = "Dark",
["/themes/ocean.json"] = "Ocean",
};
}App-wide themes from the root component
Because the injected CSS variables live on :root, a single
BptThemeLoader placed near the top of your app applies to every page.
Put it in MainLayout.razor (or any layout/root component) and bind its
source parameter to global state — for example a value held on
BptRootComponentBase or any cascading state object.
<!-- MainLayout.razor -->
<BptThemeLoader Url="@_currentThemeUrl"
OnThemeChanged="OnThemeChanged" />
<div class="page">
@Body
</div>
@code {
[CascadingParameter] public BptRootComponentBase? AppState { get; set; }
private string _currentThemeUrl = "/themes/default.json";
private void OnThemeChanged(string url)
{
// Persist the user's selection on the root state so it survives navigation.
_currentThemeUrl = url;
}
}Programmatic loading via @ref
Capture a reference and call LoadFromUrlAsync,
LoadFromBytesAsync, LoadFromBase64Async, or
ApplyConfigAsync from C#. Use this when the theme source is decided by
application logic — tenant config, A/B test bucket, an uploaded file, etc.
<BptThemeLoader @ref="_loader" AutoLoad="false" />
@code {
private BptThemeLoader? _loader;
private async Task LoadTenantTheme(string tenantId)
{
if (_loader is null) return;
await _loader.LoadFromUrlAsync($"/api/tenants/{tenantId}/theme.json");
}
}Theme JSON files should be placed in wwwroot/themes/:
wwwroot/
themes/
default.json
dark.json
ocean.json
forest.jsonEach JSON file contains key-value pairs matching the CSS custom property names:
{
"bpt-color-primary": "#1565C0",
"bpt-color-secondary": "#FF6F00",
"bpt-bg-surface": "#FFFFFF",
"bpt-text-primary": "#212121",
"bpt-border-radius": "8px"
}Dark Mode
Implementing dark mode is straightforward — create a second set of CSS variables scoped to a class or media query:
/* Dark mode via class toggle */
.dark-theme {
--bpt-bg-surface: #1E1E1E;
--bpt-bg-elevated: #2D2D2D;
--bpt-text-primary: #E0E0E0;
--bpt-text-secondary: #A0A0A0;
--bpt-color-primary: #64B5F6;
--bpt-shadow-md: 0 4px 12px rgba(0,0,0,0.4);
}
/* Or auto-detect via system preference */
@media (prefers-color-scheme: dark) {
:root {
--bpt-bg-surface: #1E1E1E;
/* ... */
}
}Advanced tutorial
Build a custom theme — end to end
A step-by-step walkthrough: from picking brand colors in the visual builder to shipping a JSON theme that switches at runtime via BptThemeLoader.