Skins & Themes PoC
This sample demonstrates the Skins & Themes system, which emulates
ASP.NET Web Forms' .skin file behavior using Blazor's cascading values.
A ThemeProvider wraps components and delivers a ThemeConfiguration
via CascadingValue. Each control automatically picks up its matching skin.
1. Default Skins
All controls inside the ThemeProvider receive the default skin for their
control type — no extra attributes needed.
2. Named Skins (SkinID)
Set SkinID on a control to select a named skin instead of the default.
This maps directly to the Web Forms SkinID attribute.
3. Explicit Values Override Theme
The theme uses StyleSheetTheme semantics: properties you set explicitly in markup always win. The theme only fills in defaults for properties left unset.
4. EnableTheming Opt-Out
Set EnableTheming="false" on any control to skip theme application entirely.
The control renders with only its explicit properties.
5. Nested ThemeProviders
An inner ThemeProvider overrides the outer theme for its subtree.
This lets you apply different themes to different sections of a page.
Outer theme (blue buttons, navy labels):
Inner theme (teal buttons, dark-green labels):
6. Without ThemeProvider
Components outside any ThemeProvider receive no theme and render
with their default appearance.
7. ThemeMode — StyleSheetTheme vs Theme
Web Forms supported two theme behaviors: Page.StyleSheetTheme (theme sets
defaults, explicit values win) and Page.Theme (theme overrides all values).
Set Mode on ThemeConfiguration to choose.
StyleSheetTheme (default)
Explicit BackColor wins over the theme.
Theme (override mode)
Theme overrides all — even explicit green becomes blue.
8. Sub-component Styles on Data Controls
Data controls like GridView expose multiple style slots — header, rows,
alternating rows, footer, and more. Use SkinBuilder.SubStyle() to theme
each section independently through ThemeConfiguration.
| ID | Product | Category | Price |
|---|---|---|---|
| 1 | Product 1 | Tools | 6.49 |
| 2 | Product 2 | Electronics | 7.99 |
| 3 | Product 3 | Hardware | 9.49 |
| 4 | Product 4 | Tools | 10.99 |
| 5 | Product 5 | Electronics | 12.49 |
Migration Guide — Before & After
The Skins & Themes system maps directly from ASP.NET Web Forms concepts.
Before (Web Forms)
App_Themes/Corporate/controls.skin:
<asp:Button runat="server"
BackColor="#336699" ForeColor="White"
Font-Names="Segoe UI" Font-Size="9pt"
BorderStyle="None" />
<asp:Button runat="server" SkinID="danger"
BackColor="#CC3333" ForeColor="White"
Font-Bold="True" />
web.config:
<pages theme="Corporate" />
Products.aspx:
<asp:Button ID="btnSave" runat="server" Text="Save" />
<asp:Button ID="btnDelete" runat="server" Text="Delete" SkinID="danger" />
After (Blazor)
// CorporateTheme.cs
var theme = new ThemeConfiguration();
theme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.FromHtml("#336699"),
ForeColor = WebColor.FromName("White"),
Font = new FontInfo { Name = "Segoe UI", Size = new FontUnit("9pt") }
});
theme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.FromHtml("#CC3333"),
ForeColor = WebColor.FromName("White"),
Font = new FontInfo { Bold = true }
}, skinId: "danger");
@* App.razor or layout *@
<ThemeProvider Theme="@corporateTheme">
<Router ... />
</ThemeProvider>
@* Products.razor — markup barely changes *@
<Button Text="Save" />
<Button Text="Delete" SkinID="danger" />
Migration Steps
- Convert each
.skinfile entry to aThemeConfiguration.AddSkin()call - Wrap your app (or page section) in a
<ThemeProvider> - Remove
asp:prefix andrunat="server"from markup - Keep
SkinIDattributes — they work identically - Use
EnableTheming="false"for controls that should ignore the theme
Source Code
@page "/ControlSamples/Theming"
@using BlazorWebFormsComponents.Theming
@using BlazorWebFormsComponents.Enums
@using SharedSampleObjects.Models
<ThemeProvider Theme="@SampleTheme">
<!-- Default skins applied automatically -->
<Button Text="Themed Button" />
<Label Text="Themed Label" />
<TextBox Text="Themed TextBox" />
<!-- Named skins via SkinID -->
<Button Text="Danger" SkinID="Danger" />
<Button Text="Success" SkinID="Success" />
<!-- Explicit values override theme -->
<Button Text="Explicit Green" BackColor="WebColor.Green" ForeColor="WebColor.White" />
<!-- Opt out of theming entirely -->
<Button Text="Opted Out" EnableTheming="false" />
</ThemeProvider>
<!-- Nested ThemeProvider overrides outer theme -->
<ThemeProvider Theme="@SampleTheme">
<ThemeProvider Theme="@AlternateTheme">
<Button Text="Inner Theme" />
</ThemeProvider>
</ThemeProvider>
<!-- Section 7: ThemeMode comparison -->
<ThemeProvider Theme="@StyleSheetThemeDemo">
<Button Text="Theme Blue" />
<Button Text="Explicit Green" BackColor="WebColor.Green" ForeColor="WebColor.White" />
</ThemeProvider>
<ThemeProvider Theme="@ThemeOverrideDemo">
<Button Text="Theme Blue" />
<Button Text="Explicit Green" BackColor="WebColor.Green" ForeColor="WebColor.White" />
</ThemeProvider>
<!-- Section 8: GridView with themed sub-component styles -->
<ThemeProvider Theme="@GridViewTheme">
<GridView ItemType="Product" Items="@_products"
AutoGenerateColumns="false" ShowFooter="true">
<Columns>
<BoundField ItemType="Product" DataField="Id" HeaderText="ID" />
<BoundField ItemType="Product" DataField="Name" HeaderText="Product" />
</Columns>
</GridView>
</ThemeProvider>
@code {
private ThemeConfiguration SampleTheme;
private ThemeConfiguration AlternateTheme;
private ThemeConfiguration StyleSheetThemeDemo;
private ThemeConfiguration ThemeOverrideDemo;
private ThemeConfiguration GridViewTheme;
private List<Product> _products;
protected override void OnInitialized()
{
SampleTheme = new ThemeConfiguration();
// Default Button skin
SampleTheme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.Blue,
ForeColor = WebColor.White
});
// Named skins
SampleTheme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.Red,
ForeColor = WebColor.White
}, "Danger");
SampleTheme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.FromHtml("#006633"),
ForeColor = WebColor.White
}, "Success");
// Label and TextBox skins
SampleTheme.AddSkin("Label", new ControlSkin
{
ForeColor = WebColor.Navy,
Font = new FontInfo { Bold = true }
});
SampleTheme.AddSkin("TextBox", new ControlSkin
{
BorderColor = WebColor.Blue,
BorderStyle = BorderStyle.Solid
});
// Alternate theme for nesting demo
AlternateTheme = new ThemeConfiguration();
AlternateTheme.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.Teal,
ForeColor = WebColor.White
});
AlternateTheme.AddSkin("Label", new ControlSkin
{
ForeColor = WebColor.FromHtml("#006633"),
Font = new FontInfo { Italic = true }
});
// Section 7: ThemeMode demos — same skins, different modes
StyleSheetThemeDemo = new ThemeConfiguration(); // defaults to StyleSheetTheme
StyleSheetThemeDemo.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.Blue, ForeColor = WebColor.White
});
StyleSheetThemeDemo.AddSkin("Label", new ControlSkin
{
ForeColor = WebColor.Navy, Font = new FontInfo { Bold = true }
});
ThemeOverrideDemo = new ThemeConfiguration { Mode = ThemeMode.Theme };
ThemeOverrideDemo.AddSkin("Button", new ControlSkin
{
BackColor = WebColor.Blue, ForeColor = WebColor.White
});
ThemeOverrideDemo.AddSkin("Label", new ControlSkin
{
ForeColor = WebColor.Navy, Font = new FontInfo { Bold = true }
});
// Section 8: GridView sub-component styles (fluent API)
GridViewTheme = new ThemeConfiguration();
GridViewTheme.ForControl("GridView", b => b
.SubStyle("HeaderStyle", s =>
{
s.BackColor = WebColor.Navy;
s.ForeColor = WebColor.White;
s.Font.Bold = true;
})
.SubStyle("AlternatingRowStyle", s =>
{
s.BackColor = WebColor.FromHtml("#F0F4F8");
})
.SubStyle("FooterStyle", s =>
{
s.BackColor = WebColor.FromHtml("#336699");
s.ForeColor = WebColor.White;
s.Font.Italic = true;
})
);
_products = Product.GetProducts(5);
}
}