multi-site-theming

Guidance for implementing per-site themes, white-labeling, and brand customization in multi-site CMS architectures.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "multi-site-theming" with this command: npx skills add melodic-software/claude-code-plugins/melodic-software-claude-code-plugins-multi-site-theming

Multi-Site Theming

Guidance for implementing per-site themes, white-labeling, and brand customization in multi-site CMS architectures.

When to Use This Skill

  • Implementing per-tenant or per-site branding

  • Designing theme inheritance hierarchies

  • Building white-label customization systems

  • Configuring dynamic theme switching

  • Managing brand overrides at runtime

Theme Architecture

Theme Hierarchy

┌─────────────────────────────────────────────────────────────────┐ │ THEME HIERARCHY │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ BASE THEME │ │ │ │ (Default colors, typography, spacing, components) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ BRAND THEME │ │ │ │ (Corporate colors, fonts, logo, brand identity) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ SITE THEME │ │ │ │ (Site-specific overrides, micro-branding) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ USER PREFERENCES │ │ │ │ (Dark/light mode, accessibility, contrast) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘

Theme Model

Core Theme Entity

public class Theme { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public string Slug { get; set; } = string.Empty; public ThemeType Type { get; set; }

// Inheritance
public Guid? ParentThemeId { get; set; }
public Theme? ParentTheme { get; set; }

// Scope
public Guid? TenantId { get; set; }  // Null = global/base
public Guid? SiteId { get; set; }    // Null = tenant-wide

// Tokens
public ThemeTokens Tokens { get; set; } = new();

// Assets
public ThemeAssets Assets { get; set; } = new();

// Metadata
public bool IsDefault { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedUtc { get; set; }
public DateTime? ModifiedUtc { get; set; }

}

public enum ThemeType { Base, // Foundation theme Brand, // Tenant/brand level Site, // Individual site Variant // Light/dark variants }

public class ThemeTokens { // Colors public ColorTokens Colors { get; set; } = new();

// Typography
public TypographyTokens Typography { get; set; } = new();

// Spacing
public SpacingTokens Spacing { get; set; } = new();

// Borders & Shadows
public BorderTokens Borders { get; set; } = new();
public ShadowTokens Shadows { get; set; } = new();

// Component-specific overrides
public Dictionary<string, Dictionary<string, string>> Components { get; set; } = new();

}

public class ColorTokens { // Brand colors public string Primary { get; set; } = "#3B82F6"; public string Secondary { get; set; } = "#6366F1"; public string Accent { get; set; } = "#F59E0B";

// Semantic colors
public string Success { get; set; } = "#10B981";
public string Warning { get; set; } = "#F59E0B";
public string Error { get; set; } = "#EF4444";
public string Info { get; set; } = "#3B82F6";

// Surface colors
public string Background { get; set; } = "#FFFFFF";
public string Surface { get; set; } = "#F9FAFB";
public string Border { get; set; } = "#E5E7EB";

// Text colors
public string TextPrimary { get; set; } = "#111827";
public string TextSecondary { get; set; } = "#6B7280";
public string TextMuted { get; set; } = "#9CA3AF";

}

public class ThemeAssets { public string? LogoUrl { get; set; } public string? LogoDarkUrl { get; set; } public string? FaviconUrl { get; set; } public string? BackgroundImageUrl { get; set; } public List<string> FontUrls { get; set; } = new(); public string? CustomCss { get; set; } }

Theme Resolution

Cascading Theme Service

public class ThemeResolver { public async Task<ResolvedTheme> ResolveAsync(ThemeContext context) { var themes = new List<Theme>();

    // 1. Load base theme
    var baseTheme = await _repository.GetBaseThemeAsync();
    if (baseTheme != null) themes.Add(baseTheme);

    // 2. Load tenant/brand theme
    if (context.TenantId.HasValue)
    {
        var tenantTheme = await _repository.GetTenantThemeAsync(context.TenantId.Value);
        if (tenantTheme != null) themes.Add(tenantTheme);
    }

    // 3. Load site theme
    if (context.SiteId.HasValue)
    {
        var siteTheme = await _repository.GetSiteThemeAsync(context.SiteId.Value);
        if (siteTheme != null) themes.Add(siteTheme);
    }

    // 4. Apply user preferences (dark mode, contrast)
    if (context.UserPreferences != null)
    {
        var variantTheme = await ResolveVariantAsync(themes.Last(), context.UserPreferences);
        if (variantTheme != null) themes.Add(variantTheme);
    }

    // Merge themes in order
    return MergeThemes(themes);
}

private ResolvedTheme MergeThemes(IEnumerable&#x3C;Theme> themes)
{
    var resolved = new ResolvedTheme();

    foreach (var theme in themes)
    {
        // Later themes override earlier ones
        MergeTokens(resolved.Tokens, theme.Tokens);
        MergeAssets(resolved.Assets, theme.Assets);
    }

    return resolved;
}

}

public class ThemeContext { public Guid? TenantId { get; set; } public Guid? SiteId { get; set; } public UserPreferences? UserPreferences { get; set; } }

public class UserPreferences { public ColorScheme ColorScheme { get; set; } = ColorScheme.System; public bool HighContrast { get; set; } public bool ReducedMotion { get; set; } }

public enum ColorScheme { Light, Dark, System }

CSS Variable Generation

Variable Generator

public class CssVariableGenerator { public string GenerateCssVariables(ResolvedTheme theme) { var sb = new StringBuilder();

    sb.AppendLine(":root {");

    // Colors
    GenerateColorVariables(sb, theme.Tokens.Colors);

    // Typography
    GenerateTypographyVariables(sb, theme.Tokens.Typography);

    // Spacing
    GenerateSpacingVariables(sb, theme.Tokens.Spacing);

    // Borders &#x26; Shadows
    GenerateBorderVariables(sb, theme.Tokens.Borders);
    GenerateShadowVariables(sb, theme.Tokens.Shadows);

    sb.AppendLine("}");

    // Dark mode variant
    if (theme.DarkVariant != null)
    {
        sb.AppendLine();
        sb.AppendLine("@media (prefers-color-scheme: dark) {");
        sb.AppendLine("  :root {");
        GenerateColorVariables(sb, theme.DarkVariant.Colors, "    ");
        sb.AppendLine("  }");
        sb.AppendLine("}");

        // Manual dark mode class
        sb.AppendLine();
        sb.AppendLine("[data-theme=\"dark\"] {");
        GenerateColorVariables(sb, theme.DarkVariant.Colors, "  ");
        sb.AppendLine("}");
    }

    return sb.ToString();
}

private void GenerateColorVariables(
    StringBuilder sb,
    ColorTokens colors,
    string indent = "  ")
{
    sb.AppendLine($"{indent}/* Brand Colors */");
    sb.AppendLine($"{indent}--color-primary: {colors.Primary};");
    sb.AppendLine($"{indent}--color-secondary: {colors.Secondary};");
    sb.AppendLine($"{indent}--color-accent: {colors.Accent};");
    sb.AppendLine();
    sb.AppendLine($"{indent}/* Semantic Colors */");
    sb.AppendLine($"{indent}--color-success: {colors.Success};");
    sb.AppendLine($"{indent}--color-warning: {colors.Warning};");
    sb.AppendLine($"{indent}--color-error: {colors.Error};");
    sb.AppendLine($"{indent}--color-info: {colors.Info};");
    sb.AppendLine();
    sb.AppendLine($"{indent}/* Surface Colors */");
    sb.AppendLine($"{indent}--color-background: {colors.Background};");
    sb.AppendLine($"{indent}--color-surface: {colors.Surface};");
    sb.AppendLine($"{indent}--color-border: {colors.Border};");
    sb.AppendLine();
    sb.AppendLine($"{indent}/* Text Colors */");
    sb.AppendLine($"{indent}--color-text-primary: {colors.TextPrimary};");
    sb.AppendLine($"{indent}--color-text-secondary: {colors.TextSecondary};");
    sb.AppendLine($"{indent}--color-text-muted: {colors.TextMuted};");
}

}

Generated CSS Output

:root { /* Brand Colors */ --color-primary: #3B82F6; --color-secondary: #6366F1; --color-accent: #F59E0B;

/* Semantic Colors */ --color-success: #10B981; --color-warning: #F59E0B; --color-error: #EF4444; --color-info: #3B82F6;

/* Surface Colors */ --color-background: #FFFFFF; --color-surface: #F9FAFB; --color-border: #E5E7EB;

/* Text Colors */ --color-text-primary: #111827; --color-text-secondary: #6B7280; --color-text-muted: #9CA3AF;

/* Typography */ --font-family-base: 'Inter', system-ui, sans-serif; --font-family-heading: 'Inter', system-ui, sans-serif; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem;

/* Spacing */ --spacing-1: 0.25rem; --spacing-2: 0.5rem; --spacing-3: 0.75rem; --spacing-4: 1rem; --spacing-6: 1.5rem; --spacing-8: 2rem; }

@media (prefers-color-scheme: dark) { :root { --color-background: #111827; --color-surface: #1F2937; --color-border: #374151; --color-text-primary: #F9FAFB; --color-text-secondary: #D1D5DB; --color-text-muted: #9CA3AF; } }

[data-theme="dark"] { --color-background: #111827; --color-surface: #1F2937; --color-border: #374151; --color-text-primary: #F9FAFB; --color-text-secondary: #D1D5DB; --color-text-muted: #9CA3AF; }

Theme API

REST Endpoints

GET /api/themes # List all themes GET /api/themes/{id} # Get theme by ID POST /api/themes # Create theme PUT /api/themes/{id} # Update theme DELETE /api/themes/{id} # Delete theme

GET /api/themes/resolve # Resolve current theme (by context) GET /api/themes/{id}/css # Get generated CSS GET /api/themes/{id}/variables # Get CSS variables JSON POST /api/themes/{id}/preview # Preview theme changes

Theme Delivery Endpoint

[Route("api/themes")] public class ThemeController : ControllerBase { [HttpGet("resolve/css")] [ResponseCache(Duration = 3600, VaryByHeader = "X-Tenant-Id,X-Site-Id")] public async Task<IActionResult> GetResolvedCss( [FromHeader(Name = "X-Tenant-Id")] Guid? tenantId, [FromHeader(Name = "X-Site-Id")] Guid? siteId) { var context = new ThemeContext { TenantId = tenantId, SiteId = siteId };

    var theme = await _resolver.ResolveAsync(context);
    var css = _generator.GenerateCssVariables(theme);

    return Content(css, "text/css");
}

[HttpGet("{id}/variables")]
public async Task&#x3C;ActionResult&#x3C;ThemeTokens>> GetVariables(Guid id)
{
    var theme = await _repository.GetByIdAsync(id);
    if (theme == null) return NotFound();

    return Ok(theme.Tokens);
}

}

White-Label Configuration

Tenant Branding Settings

public class TenantBrandingSettings { // Identity public string CompanyName { get; set; } = string.Empty; public string ProductName { get; set; } = string.Empty;

// Visual Identity
public Guid? ThemeId { get; set; }
public string? CustomDomain { get; set; }

// Logos
public string? LogoUrl { get; set; }
public string? LogoDarkUrl { get; set; }
public string? FaviconUrl { get; set; }
public string? AppIconUrl { get; set; }

// Contact
public string? SupportEmail { get; set; }
public string? SupportUrl { get; set; }

// Legal
public string? TermsUrl { get; set; }
public string? PrivacyUrl { get; set; }

// Feature Flags
public bool ShowPoweredBy { get; set; } = true;
public bool CustomEmailTemplates { get; set; }

}

Frontend Integration

Blazor Theme Provider

@inject IThemeService ThemeService

<CascadingValue Value="@CurrentTheme"> @ChildContent </CascadingValue>

@code { [Parameter] public RenderFragment? ChildContent { get; set; }

private ResolvedTheme? CurrentTheme { get; set; }

protected override async Task OnInitializedAsync()
{
    CurrentTheme = await ThemeService.GetCurrentThemeAsync();
}

}

JavaScript Theme Switching

// Theme switcher const ThemeSwitcher = { setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); },

getTheme() {
    return localStorage.getItem('theme') ||
           (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
},

init() {
    this.setTheme(this.getTheme());

    // Listen for system changes
    window.matchMedia('(prefers-color-scheme: dark)')
        .addEventListener('change', e => {
            if (!localStorage.getItem('theme')) {
                this.setTheme(e.matches ? 'dark' : 'light');
            }
        });
}

};

Related Skills

  • design-token-management

  • Token schemas and Style Dictionary

  • headless-api-design

  • Theme API delivery

  • content-type-modeling

  • Theme as content type

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

design-thinking

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

plantuml-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

system-prompt-engineering

No summary provided by upstream source.

Repository SourceNeeds Review