
Storefront Theming
- 65 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Build a themeable storefront with design tokens and CSS custom properties supporting white-labeling, multi-brand variants, and dark mode.
About
Implements a themeable storefront using design tokens and CSS custom properties for white-labeling, multi-brand variants, and dark mode. A developer uses it to run multiple brand looks from one codebase.
- Design tokens and CSS custom properties
- White-labeling, multi-brand, and dark-mode support
Storefront Theming by the numbers
- 65 all-time installs (skills.sh)
- Ranked #1,180 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill storefront-themingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Build a themeable storefront with design tokens and CSS custom properties supporting white-labeling, multi-brand variants, and dark mode.
Files
Storefront Theming
Overview
Architect a theming system using design tokens and CSS custom properties that allows a storefront to be re-skinned for multiple brands without modifying component code. Covers the token taxonomy (color, typography, spacing, radius), runtime theme switching for dark mode, and white-label multi-tenant architecture where each tenant supplies their own token overrides.
When to Use This Skill
- When building a platform that will power multiple branded storefronts from a single codebase
- When implementing dark mode for a storefront
- When a rebrand requires changing colors across thousands of component instances without hunt-and-replace
- When handing off a design system to a team that needs to maintain brand consistency
Core Instructions
Step 1: Determine the merchant's platform and choose the right approach
| Platform | Recommended Approach | Why |
|---|---|---|
| Shopify | Use the Theme Editor's Color scheme system + edit config/settings_schema.json and assets/base.css to add custom CSS variables | Shopify OS2.0 themes expose all brand colors, fonts, and border radii as Theme Editor settings that write to CSS variables automatically; no build step required |
| WooCommerce | Use the WordPress Global Styles system (WordPress 5.9+) with a block theme, or configure CSS variables in Appearance → Customize → Additional CSS for classic themes | Block themes using theme.json support a full design token system natively; classic themes need CSS custom properties added manually |
| BigCommerce | Configure theme settings in Storefront → My Themes → Customize — Cornerstone exposes colors, fonts, and spacing as Theme Editor variables; advanced customization via config.json and SCSS | Cornerstone compiles SCSS with Handlebars template variables; custom CSS variables can be added to the global stylesheet |
| Custom / Headless | Build a three-tier token system (global → semantic → component) using CSS custom properties, compiled from JSON using Style Dictionary | Full control over token taxonomy, dark mode, and multi-tenant overrides; see implementation below |
Step 2: Configure theming on your platform
---
Shopify
Using the Theme Editor color system (no code required): 1. Go to Online Store → Themes → Customize 2. Click Theme settings (paint bucket icon) in the left panel 3. Under Colors, configure your brand's:
- Primary, secondary, and accent colors
- Background colors
- Text colors
- Button colors and text
4. Under Typography, set your font families and sizes 5. Under Buttons and inputs, configure border radius for buttons and form fields 6. All changes write to CSS custom properties that every component in the theme uses — no component-level changes needed
Adding custom CSS variables (for developers extending the theme): 1. Go to Online Store → Themes → Edit code 2. Open assets/base.css (Dawn) or equivalent 3. Add your custom variables to the :root block, referencing the theme's settings variables:
:root {
--color-button: {{ settings.color_button.red }}, {{ settings.color_button.green }}, {{ settings.color_button.blue }};
--font-body-family: {{ settings.type_body_font.family }}, sans-serif;
}4. In your section/component Liquid files, use var(--color-button) to reference the merchant-controlled setting
---
WooCommerce
Block themes with theme.json (WordPress 5.9+):
If you're building a new store with a block theme (like Storefront Blocks or a custom block theme): 1. Open theme.json in your theme root 2. Add your design tokens to the settings.color.palette and settings.typography sections:
{
"settings": {
"color": {
"palette": [
{ "slug": "brand-primary", "color": "#2563eb", "name": "Brand Primary" },
{ "slug": "brand-secondary", "color": "#3b82f6", "name": "Brand Secondary" }
]
},
"custom": {
"spacing": { "xs": "0.5rem", "sm": "1rem", "md": "1.5rem" }
}
}
}3. WordPress automatically exposes these in the Global Styles panel and generates CSS custom properties like --wp--preset--color--brand-primary
Classic themes — CSS variables in Additional CSS: 1. Go to Appearance → Customize → Additional CSS 2. Define your brand variables:
:root {
--color-primary: #2563eb;
--color-price-sale: #dc2626;
--font-size-base: 1rem;
--border-radius-button: 0.375rem;
}3. Use these variables in your child theme's CSS instead of hard-coded values
---
BigCommerce
Cornerstone theme variables (SCSS-based): 1. Go to Storefront → My Themes → Edit Theme Files (requires a copy of the theme) 2. Open assets/scss/settings/global/ — Cornerstone organizes variables by category (color, typography, spacing) 3. Modify _color.scss to update the brand color palette 4. Open config.json in the theme root — this maps Theme Editor controls to SCSS variables:
{
"settings": {
"color-primary": "#2563eb",
"font-size-root": "16px",
"button-radius": "4px"
}
}5. In Storefront → My Themes → Customize, the Theme Editor reflects these settings for merchant configuration
---
Custom / Headless
Three-tier CSS custom properties system:
/* 1. Global tokens — the complete palette (never used directly in components) */
:root {
--blue-600: #2563eb;
--blue-500: #3b82f6;
--red-500: #ef4444;
--green-600: #16a34a;
--gray-50: #f8fafc;
--gray-900: #0f172a;
--space-4: 1rem;
--space-6: 1.5rem;
--radius-md: 0.375rem;
}
/* 2. Semantic tokens — meaningful aliases (components consume these) */
:root {
--color-brand-primary: var(--blue-600);
--color-brand-secondary: var(--blue-500);
--color-surface: var(--gray-50);
--color-on-surface: var(--gray-900);
--color-price: var(--gray-900);
--color-price-sale: var(--red-500);
--color-success: var(--green-600);
}
/* 3. Dark mode overrides */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-surface: var(--gray-900);
--color-on-surface: var(--gray-50);
}
}
[data-theme="dark"] {
--color-surface: var(--gray-900);
--color-on-surface: var(--gray-50);
}
/* Components only use semantic tokens */
.btn-primary {
background: var(--color-brand-primary);
border-radius: var(--radius-md);
min-height: 44px;
}
.price--sale { color: var(--color-price-sale); }Dark mode toggle (stores preference in localStorage):
export function ThemeToggle() {
const [theme, setTheme] = useState(() => localStorage.getItem('theme') ?? 'system');
function applyTheme(newTheme) {
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
if (newTheme === 'system') document.documentElement.removeAttribute('data-theme');
else document.documentElement.setAttribute('data-theme', newTheme);
}
return (
<button onClick={() => applyTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}>
{theme === 'dark' ? 'Light mode' : 'Dark mode'}
</button>
);
}Prevent dark mode flash (apply before render):
<!-- In <head>, before any stylesheets -->
<script>
(function() {
var theme = localStorage.getItem('theme');
if (theme === 'dark' || (!theme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.setAttribute('data-theme', 'dark');
})();
</script>Multi-tenant white-label (inject per-tenant overrides server-side):
// In your server response, inject tenant CSS overrides in <head>
// so they're applied before the page renders (no flash)
const tenantTheme = await getTenantTheme(req.hostname);
const cssOverrides = tenantTheme
? `:root { ${Object.entries(tenantTheme)
.map(([k, v]) => `--${k}: ${sanitizeCssValue(v)};`)
.join(' ')} }`
: '';
// Sanitize to allow only safe CSS value patterns
function sanitizeCssValue(value) {
if (/^#[0-9a-fA-F]{3,8}$/.test(value)) return value; // hex color
if (/^\d+(\.\d+)?(px|rem|em|%)$/.test(value)) return value; // length
if (/^[a-zA-Z0-9\s,'"]+$/.test(value)) return value; // font names
return ''; // reject everything else (prevents CSS injection)
}Step 3: Build a token reference for your team
Regardless of platform, document your tokens so designers and developers stay in sync:
- Shopify: The Theme Editor serves as the live token reference — share the Customize URL with your design team
- WooCommerce: Document the CSS variable names in a Storybook instance or a simple HTML reference page
- BigCommerce: Cornerstone's
config.jsondocuments available settings; add comments to SCSS files - Custom: Use Style Dictionary to generate a static token documentation page, or document in Storybook
Best Practices
- Never hard-code colors in component CSS — always use semantic tokens; a rebrand that requires global find-replace of
#2563ebis avoidable - Keep semantic token names meaning-based —
--color-brand-primarynot--color-blue-600; the hex value will change but the meaning stays - Test dark mode with real devices — macOS/iOS and Windows have different default dark mode behaviors; test both
- Validate contrast ratios — after setting brand colors, verify WCAG contrast ratios (4.5:1 for body text) using WebAIM Contrast Checker
- Sanitize white-label CSS injections — only allow hex colors, rem/px lengths, and font names; reject arbitrary strings to prevent CSS injection attacks
Common Pitfalls
| Problem | Solution |
|---|---|
| Dark mode causes flash of unstyled content | Apply data-theme attribute synchronously in a <head> script before stylesheets load |
| Tenant theme overrides not applied on first render | Inject tenant CSS as inline <style> tag server-side; do not apply in useEffect |
| Design tokens out of sync between Figma and code | Use Tokens Studio for Figma plugin to export directly to your CSS variable / theme.json files |
| White-label CSS enables XSS | Sanitize tenant token values to only allow valid CSS color, length, and font-family patterns |
| Rebranding requires changing many files | If you find yourself replacing colors in multiple component files, you skipped the semantic token layer — refactor to semantic tokens first |
Related Skills
- @responsive-storefront
- @accessibility-commerce
- @mega-menu-builder
- @product-page-design
{
"context": "Tests whether the agent implements dark mode using the correct data-theme attribute pattern, respects media query fallback with the proper :root:not() guard, uses only semantic CSS custom properties in component styles, and correctly handles theme persistence and synchronous initialization.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Semantic vars only in components",
"max_score": 12,
"description": "Component CSS files reference only semantic CSS variables (e.g., `var(--store-color-brand-primary)`) — no raw hex/rgb colors, no global tier variables like `--store-color-gray-50`"
},
{
"name": "No hard-coded values in CSS",
"max_score": 8,
"description": "Component CSS does NOT contain any hard-coded color literals (hex, rgb(), hsl()) — color values come entirely from CSS custom properties"
},
{
"name": "Dark token overrides file",
"max_score": 8,
"description": "A dark mode token file (e.g., tokens/dark.json) exists that overrides semantic color tokens for dark mode surface, text, and border colors"
},
{
"name": "data-theme attribute selector",
"max_score": 10,
"description": "Generated or hand-authored dark mode CSS applies overrides under the `[data-theme=\"dark\"]` attribute selector"
},
{
"name": "Media query with :not guard",
"max_score": 12,
"description": "Dark mode CSS includes an `@media (prefers-color-scheme: dark)` block whose rule uses `:root:not([data-theme=\"light\"])` (not just `:root`) to respect the explicit light override"
},
{
"name": "Sync head script for FOUC",
"max_score": 15,
"description": "HTML or template includes an inline `<script>` in `<head>` that reads `localStorage` and sets `data-theme` on `document.documentElement` before the body renders, preventing flash of unstyled content"
},
{
"name": "localStorage persistence",
"max_score": 10,
"description": "Theme toggle logic saves the chosen theme to `localStorage` (key: `'theme'`) and reads it back on initialization"
},
{
"name": "System default support",
"max_score": 10,
"description": "Theme toggle supports a 'system' value that removes the `data-theme` attribute from `document.documentElement` (delegating to the media query)"
},
{
"name": "data-theme on documentElement",
"max_score": 8,
"description": "Theme toggle applies light/dark by calling `document.documentElement.setAttribute('data-theme', ...)` and removes it for system preference"
},
{
"name": "No global tokens in components",
"max_score": 7,
"description": "Component styles do NOT reference global-tier token names such as `--store-color-gray-*`, `--store-color-blue-*` etc. — only semantic names"
}
]
}
Dark Mode for an Existing Storefront
Problem/Feature Description
Lumora Shop is an online fashion retailer whose storefront was built with a light theme. Customer research has shown strong demand for a dark mode, and the accessibility team has also flagged that the existing color scheme needs to be systematically managed rather than scattered through individual component files.
The team wants to introduce dark mode support while also ensuring component styles are properly abstracted. The solution should handle the user's operating system preference automatically but also let shoppers manually switch modes and have their preference remembered across sessions. A particularly important requirement from the frontend lead is that there must be no "flash" of the wrong theme when the page first loads — this was a known issue in a competitor's implementation that hurt perceived quality.
The storefront uses a token-based CSS approach where --store-color-* variables are defined on :root. A small set of component styles and an HTML page are provided for you to work with.
Output Specification
Produce the following:
1. Component CSS files (e.g., components/button.css, components/product-card.css) that style two or three components. Color and size values must come from CSS custom properties.
2. A dark mode CSS file (e.g., src/styles/dark.css or equivalent) containing the overrides that activate when dark mode is in effect, covering at minimum surface, text, and border colors.
3. An HTML page (index.html) that includes the necessary CSS, shows the components, and correctly initializes the theme on page load without a flash of the wrong colors.
4. A theme toggle component or script (e.g., ThemeToggle.jsx or theme-toggle.js) that allows the user to switch between modes and persists their choice.
5. A NOTES.md briefly describing your approach to the theme switching mechanism and how you prevent the flash of unstyled content.
Input Files
The following base token CSS is provided. Extract it before beginning.
=============== FILE: src/styles/tokens.css =============== :root { --store-color-brand-primary: #2563eb; --store-color-brand-secondary: #3b82f6; --store-color-surface: #f8fafc; --store-color-on-surface: #0f172a; --store-color-price: #0f172a; --store-color-price-sale: #ef4444; --store-color-success: #16a34a; --store-color-border: #f1f5f9; --store-button-border-radius: 0.375rem; --store-font-size-base: 1rem; --store-font-size-sm: 0.875rem; --store-space-4: 1rem; --store-space-6: 1.5rem; }
{
"context": "Tests whether the agent correctly implements server-side tenant theme injection as an inline style tag (not client-only), serializes overrides as --store- CSS custom properties, and sanitizes tenant values to prevent XSS.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Server-side injection",
"max_score": 15,
"description": "Tenant theme overrides are applied in server-side middleware or server rendering logic — not exclusively in a client-side `useEffect` or `componentDidMount`"
},
{
"name": "Inline style tag in head",
"max_score": 12,
"description": "The server renders tenant overrides as an inline `<style>` tag (e.g., `res.locals.themeOverrides` or equivalent) placed in the `<head>` of the HTML document"
},
{
"name": "store- variable prefix",
"max_score": 10,
"description": "Serialized CSS overrides use the `--store-` prefix (e.g., `--store-color-brand-primary: #e63946;`) matching the generated token naming convention"
},
{
"name": "Overrides scoped to :root",
"max_score": 8,
"description": "The injected inline style block scopes overrides to `:root { ... }` so they cascade to all components"
},
{
"name": "Sanitization present",
"max_score": 15,
"description": "Code includes a sanitization or validation step that processes tenant theme values before they are injected into HTML"
},
{
"name": "Allows color values",
"max_score": 8,
"description": "Sanitization accepts valid CSS color formats (hex like `#e63946`, or named colors) as permitted values"
},
{
"name": "Allows rem/px lengths",
"max_score": 8,
"description": "Sanitization accepts rem and px length values (e.g., `1.0625rem`, `16px`) as permitted values"
},
{
"name": "Allows border-radius values",
"max_score": 7,
"description": "Sanitization accepts border-radius values including pixel and `9999px` pill values"
},
{
"name": "Rejects arbitrary strings",
"max_score": 12,
"description": "Sanitization rejects or strips values that are not colors, lengths, or border-radius — e.g., `url(...)`, `expression(...)`, multi-value strings with semicolons, or JavaScript"
},
{
"name": "Tenant lookup from request",
"max_score": 5,
"description": "Middleware resolves the tenant from the incoming request (e.g., via hostname, subdomain, or header) before applying overrides"
}
]
}
White-Label Theme Injection for a Multi-Tenant Storefront Platform
Problem/Feature Description
Nexus Retail Cloud is a SaaS platform that hosts branded storefronts for hundreds of independent merchants. Each merchant has their own primary color, button style, and font size preferences stored in the platform's database. The engineering team needs to implement a mechanism that applies each merchant's branding automatically when their storefront is served — without rebuilding the application or shipping separate bundles per merchant.
A previous attempt used a React useEffect hook to apply brand colors on the client, but this caused a visible flash of the default (unbranded) theme on every page load, which merchants complained about loudly. The platform uses server-side rendering (Express + a templating layer), so the new implementation must apply the merchant's theme before the page reaches the browser.
A security review also flagged that injecting merchant-controlled values into HTML responses is a potential attack vector. The implementation must ensure that only safe CSS values reach the output.
Output Specification
Produce the following Node.js/Express implementation:
1. A middleware module (e.g., middleware/themeInjection.js) that resolves the tenant from the incoming request and populates response locals with an inline CSS block of token overrides.
2. A sanitization utility (e.g., utils/sanitizeThemeValues.js) that validates and cleans tenant-supplied theme values before they are rendered into HTML.
3. An HTML template snippet (e.g., views/head-fragment.html or a template string in code) showing how the injected CSS overrides are placed in the document <head>.
4. A `tenants.js` or inline mock representing at least two sample tenant theme records, so the middleware can be demonstrated without a real database.
5. A short test script (test-injection.js) that exercises the sanitization utility with a mix of valid and invalid values and logs which pass and which are rejected, so the behavior is observable in the output files.
Write a NOTES.md explaining the security approach and why server-side injection was chosen over a client-side approach.
{
"context": "Tests whether the agent correctly structures a three-tier design token system in JSON, uses Style Dictionary with appropriate configuration (prefix, output paths, file formats), and names semantic tokens based on meaning rather than visual appearance.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Global token file",
"max_score": 8,
"description": "A global token JSON file exists (e.g., tokens/global.json) containing raw values for color, font-size, spacing, radius, or shadow categories"
},
{
"name": "Semantic token file",
"max_score": 8,
"description": "A semantic token JSON file exists (e.g., tokens/semantic.json) containing meaning-bearing aliases that reference global tokens"
},
{
"name": "Value object structure",
"max_score": 8,
"description": "Token entries in all JSON files use the `{ \"value\": \"...\" }` object format (not bare strings or other shapes)"
},
{
"name": "Cross-reference syntax",
"max_score": 10,
"description": "Semantic token JSON uses curly-brace reference syntax (e.g., `{color.blue-600}`) to point to global tokens, not hard-coded hex values"
},
{
"name": "Style Dictionary package",
"max_score": 10,
"description": "The build script imports and uses the `style-dictionary` (or `StyleDictionary`) package, not a hand-rolled JSON-to-CSS converter"
},
{
"name": "Store prefix configured",
"max_score": 10,
"description": "Style Dictionary configuration includes `prefix: 'store'` so generated variables are prefixed `--store-`"
},
{
"name": "CSS output path and file",
"max_score": 8,
"description": "Style Dictionary CSS platform targets `src/styles/generated/` as buildPath and produces `tokens.css`"
},
{
"name": "CSS root selector",
"max_score": 8,
"description": "Style Dictionary CSS file config uses `format: 'css/variables'` with `options: { selector: ':root' }`"
},
{
"name": "JS token output",
"max_score": 8,
"description": "Style Dictionary JS platform produces a `tokens.js` file using `format: 'javascript/es6'`"
},
{
"name": "Meaning-based semantic names",
"max_score": 12,
"description": "Semantic token names describe purpose or role (e.g., `brand-primary`, `surface`, `on-surface`, `price-sale`) rather than the color value they currently hold (e.g., `blue-600`, `gray-50`)"
},
{
"name": "No raw values in semantics",
"max_score": 10,
"description": "Semantic token JSON entries do NOT contain raw hex, rgb, or hard-coded rem values — they reference global tokens via the curly-brace syntax"
}
]
}
Design Token Foundation for a Multi-Brand Storefront
Problem/Feature Description
Meridian Commerce is launching a platform that will power storefronts for three distinct retail brands from a single shared codebase. The design team has produced a palette of colors, type scales, spacing values, and border radii that will be the foundation of the visual system. However, each brand will need to swap colors, adjust button shapes, and update font sizes independently — without touching any component code.
The engineering team has been asked to set up the design token infrastructure before component development begins. This means establishing a structured token source of truth in JSON and wiring up a build step that compiles those tokens into CSS custom properties and JavaScript exports for runtime access. The goal is that a future rebrand can be done entirely by updating token values, never touching component files.
Output Specification
Produce a working token system with the following deliverables:
1. Token JSON source files in a tokens/ directory. Include at minimum:
- A file for foundational/palette values (colors, type sizes, spacing, radius)
- A file for meaning-bearing aliases (brand colors, surface colors, text colors, price colors)
2. A build script (e.g., build-tokens.js or build-tokens.mjs) that reads the token JSON files and compiles them to CSS and JavaScript output.
3. Generated output files (or evidence that the build would produce them) demonstrating what the compiled token output looks like — at minimum a CSS file containing custom properties and a JavaScript module exporting token values.
4. A `package.json` (or relevant config) listing the token build dependency so another developer can reproduce the build.
Write a brief NOTES.md explaining the token architecture decisions you made — in particular how the two tiers of token files relate to each other and why.
{
"name": "finsi/storefront-theming",
"version": "0.1.0",
"summary": "Theme architecture with design tokens, CSS custom properties, and white-labeling",
"skills": {
"storefront-theming": {
"path": "SKILL.md"
}
}
}