Emblem Theme System Implementation
Implemented a complete multi-mode theming system for banner-site following the dark-matter pattern. The 'emblem' theme supports light, dark, and vibrant modes with semantic color tokens, effect tokens, and CSS variable-based architecture.
Changelog - 2026-01-19 (#1)
Emblem Theme System Implementation
Overview
Implemented a complete multi-mode theming system for banner-site (Banner VC) using the established dark-matter pattern. The theme is named "emblem" to keep the client name out of the codebase. Supports three modes: light (clean, professional), dark (bold, confident), and vibrant (high-impact expressive).
Architecture
Theme Token Hierarchy
:root (Brand-level named colors)
├── --color-crimson (#B91C1C) Primary red
├── --color-crimson-bright (#DC2626) Brighter red for dark modes
├── --color-haiti-blue (#141531) Dark mode background
├── --color-navy (#1D4ED8) Secondary blue
├── --color-navy-bright (#2563EB) Brighter blue for dark modes
├── --color-amber (#B45309) Accent warm
├── --color-electric (#60A5FA) Electric blue for vibrant
├── --color-ivory (#F9FAFB) Light mode background
├── --color-midnight (#172554) Alternative dark background
├── --color-charcoal (#171717) Light mode text
├── --color-snow (#FFFFFF) Dark mode text
└── --color-graphite-* (50-950) Neutral grey scale
[data-theme="emblem"][data-mode="light|dark|vibrant"]
├── --color-background Core background
├── --color-foreground Core text color
├── --color-surface Card/panel backgrounds
├── --color-surface-muted Subdued surfaces
├── --color-primary Primary action color
├── --color-primary-foreground Text on primary
├── --color-secondary Secondary action color
├── --color-accent Accent highlights
├── --color-muted Muted backgrounds
├── --color-muted-foreground Subdued text
├── --color-border Border color
├── --color-input Input backgrounds
├── --color-ring Focus ring color
└── Effect tokens (--fx-*)
├── --fx-glow-opacity
├── --fx-glow-spread
├── --fx-glow-color
├── --fx-text-shadow
├── --fx-hero-gradient
├── --fx-hero-bg
├── --fx-card-bg
├── --fx-card-border
├── --fx-card-shadow
└── --fx-headline-gradientKey Technical Decisions
1. Tailwind 4 Integration
Used @tailwindcss/vite plugin (not deprecated @astrojs/tailwind):
// astro.config.mjs
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});2. Custom Color Utilities with @theme inline
Tailwind 4 requires explicit registration of custom colors for utility generation. Used @theme inline to enable utilities like bg-background/80:
@theme inline {
--color-background: var(--color-background);
--color-foreground: var(--color-foreground);
--color-surface: var(--color-surface);
/* ... etc */
}3. color-mix() for Opacity Variants
Since Tailwind can't compute opacity on CSS variable colors, we defined custom utilities using color-mix():
@layer utilities {
.bg-background\/80 {
background-color: color-mix(in srgb, var(--color-background) 80%, transparent);
}
.border-border\/20 {
border-color: color-mix(in srgb, var(--color-border) 20%, transparent);
}
}4. Derived Colors from Background
To ensure changing --color-background propagates throughout the theme, surface and effect colors are derived using color-mix():
[data-theme="emblem"][data-mode="dark"] {
--color-background: var(--color-haiti-blue);
/* Derived from background */
--color-surface: color-mix(in srgb, var(--color-background) 85%, white);
--color-surface-muted: color-mix(in srgb, var(--color-background) 70%, black);
--color-muted: color-mix(in srgb, var(--color-background) 80%, white);
--color-border: color-mix(in srgb, var(--color-background) 70%, white);
/* Effect tokens also derived */
--fx-hero-bg: var(--color-background);
--fx-card-bg: color-mix(in srgb, var(--color-background) 80%, transparent);
}Files Created
src/styles/global.css
Base Tailwind 4 setup with:
@import "tailwindcss";@theme inlineblock for custom color registrationBreakpoint CSS variables
Typography system variables
Transition variables for smooth theme changes
Custom utility classes for semantic colors
Responsive visibility utilities
src/styles/themes/emblem-theme.css
Complete theme definition with:
:rootblock with brand-level named colorsLight mode semantic tokens and effect tokens
Dark mode semantic tokens and effect tokens
Vibrant mode semantic tokens and effect tokens
Helper classes (
.card,.btn-primary,.btn-secondary)Hero section helpers (
.hero-shell,.hero-panel)Glass morphism variant (
.card-glass)Gradient text effect (
.gradient-text)
src/layouts/BaseThemeLayout.astro
Layout wrapper that:
Sets
<html class="theme-emblem" data-theme="emblem" data-mode={mode}>Imports global.css and emblem-theme.css
Includes Header component
Props:
title,description,mode,hideHeader
src/components/ui/ModeToggle.astro
Mode toggle component with:
Three icons: sun (light), moon (dark), star (vibrant)
LocalStorage persistence via
emblem-modekeySystem preference detection on initial load
Click cycles through modes: light → dark → vibrant → light
src/components/basics/Header.astro
Navigation header with:
Sticky positioning with backdrop blur
Brand logo and site title
Desktop navigation with dropdown menu
Mobile hamburger menu with slide-in animation
Mode toggle integration
Emblem-specific styling (red/blue gradient underlines)
src/pages/test.astro
Test page demonstrating:
All three theme modes
Color token swatches
Card components
Button variants
Glass morphism card
Hero section
Gradient text effect
Mode Characteristics
| Mode | Background | Primary | Effects |
| Light | Ivory (#F9FAFB) | Crimson (#B91C1C) | Minimal shadows |
| Dark | Haiti Blue (#141531) | Crimson Bright (#DC2626) | Moderate glow |
| Vibrant | Haiti Blue (#141531) | Loud Red (#EF4444) | Maximum glow, dramatic shadows |
Usage
Basic Page
---
import BaseThemeLayout from '../layouts/BaseThemeLayout.astro';
---
<BaseThemeLayout title="My Page" mode="dark">
<div class="hero-shell">
<section class="container mx-auto px-4 py-16">
<h1 class="gradient-text text-4xl font-bold">Hello</h1>
<div class="card p-6">Card content</div>
<button class="btn-primary">Action</button>
</section>
</div>
</BaseThemeLayout>Changing Theme Background
Edit the dark mode --color-background in emblem-theme.css:
[data-theme="emblem"][data-mode="dark"] {
--color-background: var(--color-haiti-blue); /* Change this */
}All derived colors (surface, muted, border, card backgrounds) will automatically update.
Known Issues / Future Work
Hardcoded Colors to Address
Several effect tokens still use hardcoded rgba values instead of referencing theme variables:
Light mode glow colors:
rgba(185, 28, 28, ...)should usecolor-mix(in srgb, var(--color-crimson) X%, transparent)Vibrant mode primary colors:
#EF4444,#3B82F6,#FBBF24should be defined as named variables (--color-crimson-loud,--color-navy-loud,--color-amber-loud)Muted foreground in dark mode:
#94A3B8should reference graphite scaleHero panel gradients: Use
var(--color-primary)andvar(--color-secondary)with color-mix instead of hardcoded rgba
Dependencies
tailwindcss: ^4.1.18@tailwindcss/vite: ^4.1.18astro: ^5.16.11
No additional npm packages required. Theme is pure CSS with CSS variables.
