Skip to content

Prompt Template 15: Multi-Theme Override

Template for handling theme-specific visual overrides when a component needs to look different for specific hotels.


Read these files before starting:

docs/dev-frontend-guides/05_BEM_SASS_THEMING.md (BEM + SASS theming guide)
docs/prd/05_DESIGN_SYSTEM.md (design system specification)
packages/themes/src/_base.css (base CSS custom properties)
packages/themes/src/{AFFECTED_THEME_FILES} (theme files needing overrides)
packages/ui/src/modules/{COMPONENT_NAME}/{COMPONENT_NAME}.module.scss

I need to implement theme-specific visual overrides for the {COMPONENT_NAME}
module ({MODULE_ALIAS}).
## Component Overview
{COMPONENT_DESCRIPTION}
## Which Hotels Need Overrides
| Hotel (siteKey) | What Differs |
|---------------------|----------------------------------------------------|
| {SITE_KEY_1} | {DIFFERENCE_DESCRIPTION_1} |
| {SITE_KEY_2} | {DIFFERENCE_DESCRIPTION_2} |
| All others | Default appearance (no override needed) |
## Figma References
- Default design: {FIGMA_LINK_DEFAULT}
- {SITE_KEY_1} variant: {FIGMA_LINK_VARIANT_1}
- {SITE_KEY_2} variant: {FIGMA_LINK_VARIANT_2}
## Override Classification
For each hotel override, classify the type:
### {SITE_KEY_1}: {TOKEN_LEVEL / SCSS_MODIFIER / COMPONENT_VARIANT}
{DETAILED_DESCRIPTION_OF_DIFFERENCES}
Differences:
- Layout: {SAME / DIFFERENT — describe}
- Spacing: {SAME / DIFFERENT — describe}
- Colors: {SAME / DIFFERENT — describe}
- Typography: {SAME / DIFFERENT — describe}
- Shadows/Borders: {SAME / DIFFERENT — describe}
- Structure (HTML): {SAME / DIFFERENT — describe}
### {SITE_KEY_2}: {TOKEN_LEVEL / SCSS_MODIFIER / COMPONENT_VARIANT}
{DETAILED_DESCRIPTION_OF_DIFFERENCES}
## Strategy Hierarchy
Apply the LEAST invasive approach:
1. **Token-level override (preferred)** — Change CSS custom property values in
the hotel's theme file. No component SCSS changes needed. Use when only
colors, spacing values, or font sizes differ.
2. **SCSS theme modifier** — Use `[data-theme="{site-key}"] .component` or
`.component--theme-{site-key}` selector in the component SCSS. Use when
the CSS rules differ (e.g., different background type, border vs no border)
but the HTML structure is the same.
3. **Component prop variant** — Pass a `variant` prop that changes the
rendered HTML structure. Use only when the structural difference is
significant (different number of elements, different layout grid).
## Requirements
- Use the least invasive override approach
- No hardcoded color/spacing values in component SCSS (always use CSS custom properties)
- All 8 hotel themes must still render correctly after changes
- Each theme variant has a dedicated Storybook story
- Colour contrast passes WCAG AA in all variants
- Override is documented in the component's story file

  • Override uses the least invasive approach from the strategy hierarchy
  • No hardcoded colour, spacing, or font values in component SCSS
  • All values reference CSS custom properties (e.g., var(--color-primary))
  • All 8 hotel themes render correctly (not just the overridden ones)
  • Token-level overrides are placed in the correct theme file under [data-theme="{site-key}"]
  • SCSS modifiers (if used) follow BEM convention or [data-theme] attribute selector
  • Component prop variants (if used) are typed in the props interface
  • Dedicated Storybook story for each theme variant
  • Colour contrast meets WCAG AA (4.5:1 for text, 3:1 for large text) in all variants
  • No visual regression in unaffected themes (test by switching data-theme attribute)
  • Override approach documented in a comment in the SCSS file

  1. Hardcoding hotel colours in component SCSS. Never write color: #8B6F47 in component styles. Always use CSS custom properties like var(--color-primary). The theme file sets the property value.

  2. Using JavaScript to detect theme. Theme detection belongs in CSS via [data-theme] selectors. Do not read siteKey in a component to conditionally apply classes for visual styling.

  3. Creating separate components per hotel. Do not create M15CTABannerSavoy.tsx and M15CTABannerRoyal.tsx. Use variants, modifiers, or token overrides on a single component.

  4. Forgetting to test OTHER themes after adding an override. Adding [data-theme="savoy-palace"] rules can inadvertently affect specificity for other themes. Always verify all 8 themes still render correctly.

  5. Over-qualifying selectors. [data-theme="savoy-palace"] .m15-cta-banner is sufficient. Do not add unnecessary parent selectors that increase specificity beyond what is needed.

  6. Mixing override strategies. Pick one strategy per override. Do not combine token-level changes with SCSS modifiers for the same property on the same hotel — it creates confusion about which layer controls the value.

  7. Forgetting dark/light mode interaction. Some themes may have light and dark variants. Ensure overrides work in both if applicable.

  8. Not checking colour contrast after override. A colour that passes WCAG on a white background may fail on the hotel’s specific background colour. Always verify contrast in context.


Component description: A call-to-action banner with heading, body text, CTA button, and background treatment. Appears on landing pages.

Hotels needing overrides:

Hotel (siteKey)What Differs
savoy-palaceFull-width gradient background (gold to transparent)
royal-savoyBordered card style with shadow, not full-width
All othersDefault solid background using --color-surface-accent

Override classification:

  • savoy-palace: SCSS modifier (different background type — gradient vs solid)
  • royal-savoy: SCSS modifier (border + shadow + constrained width)

Implementation:

Step 1: Verify base component uses tokens only

Section titled “Step 1: Verify base component uses tokens only”
M15CTABanner.module.scss
.m15-cta-banner {
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-surface-accent);
text-align: center;
&__heading {
font-family: var(--font-heading);
font-size: var(--font-size-2xl);
color: var(--color-text-primary);
margin-bottom: var(--spacing-sm);
}
&__body {
font-family: var(--font-body);
font-size: var(--font-size-md);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-lg);
max-width: 60ch;
margin-inline: auto;
}
&__cta {
// Uses shared button component, no styles here
}
}
// Theme overrides — Savoy Palace
// Uses gradient background instead of solid colour
[data-theme="savoy-palace"] {
.m15-cta-banner {
background: linear-gradient(
135deg,
var(--color-accent-gold) 0%,
transparent 60%
),
var(--color-surface-accent);
}
}
// Theme overrides — Royal Savoy
// Uses bordered card style with constrained width
[data-theme="royal-savoy"] {
.m15-cta-banner {
background-color: var(--color-surface-primary);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
max-width: 800px;
margin-inline: auto;
}
}

Step 3: Add Storybook stories for each variant

Section titled “Step 3: Add Storybook stories for each variant”
M15CTABanner.stories.tsx
export const Default: Story = {
decorators: [withTheme('default')],
};
export const SavoyPalace: Story = {
decorators: [withTheme('savoy-palace')],
parameters: {
docs: {
description: {
story: 'Savoy Palace uses a gold gradient background.',
},
},
},
};
export const RoyalSavoy: Story = {
decorators: [withTheme('royal-savoy')],
parameters: {
docs: {
description: {
story: 'Royal Savoy uses a bordered card style with shadow.',
},
},
},
};
// Verify all other themes still look correct
export const AllThemes: Story = {
decorators: [withAllThemes()],
parameters: {
docs: {
description: {
story: 'All 8 hotel themes side by side for visual regression check.',
},
},
},
};

Key decisions in this example:

  • Both overrides are SCSS modifiers (not token-level) because the background type changes, not just the colour value
  • [data-theme] attribute selector used instead of BEM modifier — the component does not need to know which theme is active
  • No JavaScript involved — pure CSS handles the visual differentiation
  • Gradient in Savoy Palace still references --color-accent-gold token, not a hardcoded hex value
  • Royal Savoy border uses --color-border-subtle and --shadow-md tokens
  • AllThemes story enables quick visual regression checking across all hotels
  • No structural HTML changes needed, so component prop variant was not used