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.

Themed Label

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.

Theme Label (Navy) Explicit Red Label

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):

Outer Label

Inner theme (teal buttons, dark-green labels):

Inner Label

6. Without ThemeProvider

Components outside any ThemeProvider receive no theme and render with their default appearance.

No Theme Label

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 Navy Explicit Red

Theme (override mode)

Theme overrides all — even explicit green becomes blue.

Theme Navy Explicit Red

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.

Themed Product Catalog
IDProductCategoryPrice
1Product 1Tools6.49
2Product 2Electronics7.99
3Product 3Hardware9.49
4Product 4Tools10.99
5Product 5Electronics12.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

  1. Convert each .skin file entry to a ThemeConfiguration.AddSkin() call
  2. Wrap your app (or page section) in a <ThemeProvider>
  3. Remove asp: prefix and runat="server" from markup
  4. Keep SkinID attributes — they work identically
  5. 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);
    }
}