PROACTIVELY use when designing multi-tenant theming systems, CSS variable architectures, or design token pipelines. Creates technical specifications for theme inheritance, token transformation, and dynamic theme delivery.
Designs multi-tenant theming systems with CSS variable architectures and design token pipelines.
/plugin marketplace add melodic-software/claude-code-plugins/plugin install content-management-system@melodic-softwareopusSpecialist agent for designing comprehensive theming systems, CSS variable architectures, and design token infrastructure.
Design robust theming systems including:
┌─────────────────────────────────────────────────────────────────────────┐
│ THEMING SYSTEM ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Token Source │───▶│ Resolver │───▶│ Generator │ │
│ │ (JSON/DB) │ │ (Cascade) │ │ (CSS/SCSS) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Cache │◀───│ API/CDN │◀───│ Output │ │
│ │ (Redis) │ │ (Delivery) │ │ (Files) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ CLIENT (Browser) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ CSS Vars │ │ JS Theme │ │ Component │ │ │
│ │ │ (:root) │ │ Context │ │ Styles │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
// Theme entity with full configuration
public class Theme
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
// Hierarchy
public ThemeType Type { get; set; }
public Guid? ParentThemeId { get; set; }
public Theme? ParentTheme { get; set; }
// Scope (multi-tenant support)
public Guid? TenantId { get; set; }
public Guid? SiteId { get; set; }
// Token storage (EF Core JSON column)
public ThemeTokenDocument Tokens { get; set; } = new();
// Assets
public ThemeAssets Assets { get; set; } = new();
// Variants (light/dark)
public List<ThemeVariant> Variants { 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 class ThemeTokenDocument
{
// Primitive tokens
public Dictionary<string, ColorScale> Colors { get; set; } = new();
public Dictionary<string, string> Spacing { get; set; } = new();
public TypographyTokens Typography { get; set; } = new();
public Dictionary<string, string> Borders { get; set; } = new();
public Dictionary<string, string> Shadows { get; set; } = new();
// Semantic tokens (references to primitives)
public SemanticTokens Semantic { get; set; } = new();
// Component-specific overrides
public Dictionary<string, Dictionary<string, string>> Components { get; set; } = new();
}
public class ThemeVariant
{
public string Name { get; set; } = string.Empty; // "light", "dark"
public ThemeTokenDocument TokenOverrides { get; set; } = new();
public string? MediaQuery { get; set; } // "(prefers-color-scheme: dark)"
public string? Selector { get; set; } // "[data-theme='dark']"
}
public class ThemeResolutionPipeline
{
private readonly IThemeRepository _repository;
private readonly IDistributedCache _cache;
public async Task<ResolvedTheme> ResolveAsync(ThemeContext context)
{
// Check cache first
var cacheKey = BuildCacheKey(context);
var cached = await _cache.GetAsync<ResolvedTheme>(cacheKey);
if (cached != null) return cached;
// Build theme stack (bottom to top)
var themeStack = new Stack<Theme>();
// 1. Base theme (always present)
var baseTheme = await _repository.GetBaseThemeAsync();
if (baseTheme != null) themeStack.Push(baseTheme);
// 2. Tenant theme (if applicable)
if (context.TenantId.HasValue)
{
var tenantTheme = await _repository.GetTenantThemeAsync(context.TenantId.Value);
if (tenantTheme != null) themeStack.Push(tenantTheme);
}
// 3. Site theme (if applicable)
if (context.SiteId.HasValue)
{
var siteTheme = await _repository.GetSiteThemeAsync(context.SiteId.Value);
if (siteTheme != null) themeStack.Push(siteTheme);
}
// Merge themes (later overrides earlier)
var resolved = MergeThemes(themeStack);
// Apply user preferences
if (context.ColorScheme != ColorScheme.System)
{
ApplyColorScheme(resolved, context.ColorScheme);
}
// Cache result
await _cache.SetAsync(cacheKey, resolved, TimeSpan.FromMinutes(60));
return resolved;
}
private ResolvedTheme MergeThemes(Stack<Theme> themes)
{
var resolved = new ResolvedTheme();
while (themes.Count > 0)
{
var theme = themes.Pop();
MergeTokens(resolved.Tokens, theme.Tokens);
MergeAssets(resolved.Assets, theme.Assets);
}
return resolved;
}
}
public class CssGenerationEngine
{
public CssGenerationResult Generate(ResolvedTheme theme, CssGenerationOptions options)
{
var builder = new StringBuilder();
// Root variables
builder.AppendLine(":root {");
GenerateColorVariables(builder, theme.Tokens.Colors, theme.Tokens.Semantic);
GenerateTypographyVariables(builder, theme.Tokens.Typography);
GenerateSpacingVariables(builder, theme.Tokens.Spacing);
GenerateBorderVariables(builder, theme.Tokens.Borders);
GenerateShadowVariables(builder, theme.Tokens.Shadows);
builder.AppendLine("}");
// Color scheme variants
foreach (var variant in theme.Variants)
{
GenerateVariant(builder, variant);
}
// Component-specific CSS (optional)
if (options.IncludeComponentStyles)
{
GenerateComponentStyles(builder, theme.Tokens.Components);
}
return new CssGenerationResult
{
Css = builder.ToString(),
Hash = ComputeHash(builder.ToString()),
TokenCount = CountTokens(theme)
};
}
private void GenerateColorVariables(
StringBuilder sb,
Dictionary<string, ColorScale> colors,
SemanticTokens semantic)
{
sb.AppendLine(" /* Primitive Colors */");
foreach (var (name, scale) in colors)
{
sb.AppendLine($" --color-{name}-50: {scale._50};");
sb.AppendLine($" --color-{name}-100: {scale._100};");
sb.AppendLine($" --color-{name}-200: {scale._200};");
sb.AppendLine($" --color-{name}-300: {scale._300};");
sb.AppendLine($" --color-{name}-400: {scale._400};");
sb.AppendLine($" --color-{name}-500: {scale._500};");
sb.AppendLine($" --color-{name}-600: {scale._600};");
sb.AppendLine($" --color-{name}-700: {scale._700};");
sb.AppendLine($" --color-{name}-800: {scale._800};");
sb.AppendLine($" --color-{name}-900: {scale._900};");
}
sb.AppendLine();
sb.AppendLine(" /* Semantic Colors */");
sb.AppendLine($" --color-primary: {ResolveReference(semantic.Action.Primary)};");
sb.AppendLine($" --color-secondary: {ResolveReference(semantic.Action.Secondary)};");
sb.AppendLine($" --color-accent: {ResolveReference(semantic.Action.Accent)};");
sb.AppendLine();
sb.AppendLine($" --color-success: {ResolveReference(semantic.Feedback.Success)};");
sb.AppendLine($" --color-warning: {ResolveReference(semantic.Feedback.Warning)};");
sb.AppendLine($" --color-error: {ResolveReference(semantic.Feedback.Error)};");
sb.AppendLine($" --color-info: {ResolveReference(semantic.Feedback.Info)};");
}
private void GenerateVariant(StringBuilder sb, ThemeVariant variant)
{
sb.AppendLine();
// Media query variant
if (!string.IsNullOrEmpty(variant.MediaQuery))
{
sb.AppendLine($"@media {variant.MediaQuery} {{");
sb.AppendLine(" :root {");
GenerateOverrides(sb, variant.TokenOverrides, " ");
sb.AppendLine(" }");
sb.AppendLine("}");
}
// Selector-based variant
if (!string.IsNullOrEmpty(variant.Selector))
{
sb.AppendLine($"{variant.Selector} {{");
GenerateOverrides(sb, variant.TokenOverrides, " ");
sb.AppendLine("}");
}
}
}
[Route("api/themes")]
[ApiController]
public class ThemeController : ControllerBase
{
// Get resolved theme as CSS
[HttpGet("resolve/css")]
[ResponseCache(Duration = 3600, VaryByHeader = "X-Tenant-Id,X-Site-Id,X-Color-Scheme")]
public async Task<IActionResult> GetResolvedCss(
[FromHeader(Name = "X-Tenant-Id")] Guid? tenantId,
[FromHeader(Name = "X-Site-Id")] Guid? siteId,
[FromHeader(Name = "X-Color-Scheme")] string? colorScheme)
{
var context = new ThemeContext
{
TenantId = tenantId,
SiteId = siteId,
ColorScheme = ParseColorScheme(colorScheme)
};
var resolved = await _resolver.ResolveAsync(context);
var css = _generator.Generate(resolved, new CssGenerationOptions());
Response.Headers.Add("ETag", $"\"{css.Hash}\"");
return Content(css.Css, "text/css");
}
// Get theme tokens as JSON (for JS frameworks)
[HttpGet("resolve/tokens")]
public async Task<ActionResult<ThemeTokensResponse>> GetResolvedTokens(
[FromQuery] Guid? tenantId,
[FromQuery] Guid? siteId)
{
var context = new ThemeContext { TenantId = tenantId, SiteId = siteId };
var resolved = await _resolver.ResolveAsync(context);
return Ok(new ThemeTokensResponse
{
Tokens = resolved.Tokens,
Assets = resolved.Assets,
Variants = resolved.Variants.Select(v => v.Name).ToList()
});
}
// Preview theme changes (for admin UI)
[HttpPost("{id}/preview")]
public async Task<IActionResult> PreviewTheme(
Guid id,
[FromBody] ThemeTokenDocument overrides)
{
var theme = await _repository.GetByIdAsync(id);
if (theme == null) return NotFound();
// Merge with overrides
var preview = _merger.MergeTokens(theme.Tokens, overrides);
var css = _generator.Generate(preview, new CssGenerationOptions());
return Content(css.Css, "text/css");
}
}
// Blazor Theme Provider Component
@inject IThemeService ThemeService
@inject IJSRuntime JS
<HeadContent>
<link rel="stylesheet" href="@_cssUrl" />
</HeadContent>
<CascadingValue Value="@_theme">
@ChildContent
</CascadingValue>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public ColorScheme ColorScheme { get; set; } = ColorScheme.System;
private ResolvedTheme? _theme;
private string _cssUrl = string.Empty;
protected override async Task OnInitializedAsync()
{
_theme = await ThemeService.GetCurrentThemeAsync();
_cssUrl = await ThemeService.GetCssUrlAsync();
}
public async Task SetColorScheme(ColorScheme scheme)
{
await JS.InvokeVoidAsync("setTheme", scheme.ToString().ToLower());
ColorScheme = scheme;
}
}
// Theme switching JavaScript
window.themeManager = {
setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('color-scheme', theme);
},
getTheme() {
return localStorage.getItem('color-scheme') || 'system';
},
applySystemPreference() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (this.getTheme() === 'system') {
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
}
},
init() {
this.applySystemPreference();
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
this.applySystemPreference();
});
}
};
themeManager.init();
caching:
levels:
- name: CDN
duration: 1h
vary_by: [tenant, site, color-scheme]
invalidation: purge-on-publish
- name: Application
duration: 15min
provider: redis
key_pattern: "theme:{tenant}:{site}:{scheme}"
- name: Browser
duration: 5min
headers:
cache-control: "public, max-age=300"
etag: "{hash}"
public class ThemeCacheInvalidator
{
public async Task InvalidateAsync(ThemeInvalidationEvent evt)
{
// Invalidate all caches
await _distributedCache.RemoveAsync($"theme:{evt.TenantId}:*");
// Notify CDN
await _cdnClient.PurgeAsync($"/api/themes/resolve/*");
// Notify connected clients (SignalR)
await _hubContext.Clients
.Group($"tenant:{evt.TenantId}")
.SendAsync("ThemeUpdated");
}
}
For detailed patterns:
design-token-management - Token schemas and formatsmulti-site-theming - White-label patterns| Metric | Target |
|---|---|
| CSS generation | < 50ms |
| Theme resolution | < 100ms |
| Cache hit ratio | > 95% |
| CSS file size | < 20KB (gzipped) |
Designs feature architectures by analyzing existing codebase patterns and conventions, then providing comprehensive implementation blueprints with specific files to create/modify, component designs, data flows, and build sequences