Skip to content
SalesforceSkills

Branding & Themes

Customize colors and branding to match your company. Supports light mode, dark mode, and multi-brand setups.

Light mode firstExtend, never overrideSemantic namingTheme at the boundaryPortable

Skill Details

Install this skill

Versionv1.1.0AuthorJorge ArteagaLicenseMITSections11

Works with

Claude CodeCursorWindsurf

Create and apply custom themes that extend SLDS 2 for branded Salesforce experiences. Build theme systems that support multiple brands and component-level overrides — all without breaking the design system. Light mode is the default; dark mode is an optional enhancement when explicitly requested.

Core Principles

1
Light mode first — all themes default to light mode; dark mode is opt-in
2
Extend, never override — add brand tokens alongside SLDS hooks, don't reassign them
3
Semantic naming — brand tokens describe purpose, not color (--brand-primary, not --brand-blue)
4
Theme at the boundary — apply themes at the :host level, not deep in component CSS
5
Portable — themes work in internal Lightning, Experience Cloud, and mobile

Theme Architecture

┌─────────────────────────────────────────────┐
│  Brand Theme Layer (your custom tokens)     │  --brand-*, --app-*
├─────────────────────────────────────────────┤
│  SLDS 2 Global Hooks (Salesforce)           │  --slds-g-*
├─────────────────────────────────────────────┤
│  Component CSS (consumes both layers)       │  var(--brand-primary, var(--slds-g-...))
└─────────────────────────────────────────────┘

Custom brand tokens sit above SLDS hooks. Components consume brand tokens first, falling back to SLDS hooks, then to literal values.

Brand Token System

Defining Brand Tokens

Define brand tokens at the :host level of your root/wrapper component:

CSS
: host {
    /* Brand palette */
    --brand-primary: #1a73e8;
    --brand-primary-hover: #1557b0;
    --brand-primary-contrast: #ffffff;
    --brand-secondary: #34a853;
    --brand-secondary-contrast: #ffffff;

    /* Semantic tokens */
    --brand-surface: var(--slds-g-color-surface-1, #ffffff);
    --brand-surface-elevated: var(--slds-g-color-surface-1, #ffffff);
    --brand-text: var(--slds-g-color-on-surface-1, #181818);
    --brand-text-muted: var(--slds-g-color-on-surface-2, #444444);
    --brand-border: var(--slds-g-color-border-1, #e5e5e5);

    /* Spacing overrides (if brand has tighter/looser density) */
    --brand-spacing-unit: var(--slds-g-spacing-4, 1rem);
    --brand-radius: var(--slds-g-radius-border-2, 0.25rem);
}

Consuming Brand Tokens

CSS
.branded-card {
    background: var(--brand-surface, var(--slds-g-color-surface-1, #ffffff));
    border: var(--slds-g-sizing-border-1) solid var(--brand-border, var(--slds-g-color-border-1));
    border-radius: var(--brand-radius, var(--slds-g-radius-border-2, 0.25rem));
}

.branded-button {
    background: var(--brand-primary, var(--slds-g-color-accent-1, #0176d3));
    color: var(--brand-primary-contrast, var(--slds-g-color-on-accent-1, #ffffff));
    border-radius: var(--brand-radius, var(--slds-g-radius-border-2, 0.25rem));
}

.branded-button: hover {
    background: var(--brand-primary-hover, var(--slds-g-color-accent-2, #014486));
}

Fallback Chain

Always use a three-level fallback:

Code
var(--brand-token, var(--slds-g-token, literal-fallback))

This ensures the component works in:

1
Branded context (brand tokens defined)
2
Standard Salesforce (SLDS hooks only)
3
Degraded environment (literal fallback)

Customer Branding in 5 Minutes

For SEs who need to quickly apply a prospect's brand colors to a demo.

Quick Brand Swap

JavaScript
class="code-comment">// brandConfig.js — single file to change for each demo
const DEMO_BRAND = {
    name: class="code-string">'Customer Name',
    primary: class="code-string">'#1a73e8',        class="code-comment">// Pull from customerclass="code-string">'s website
    primaryHover: '#1557b0class="code-string">',   class="code-comment">// Darken primary by ~15%
    primaryContrast: '#ffffffclass="code-string">',
    secondary: '#34a853class="code-string">',
    logo: '/resource/customerLogoclass="code-string">',
    radius: '0.5rem'           class="code-comment">// Rounded = modern, 0 = corporate
};

export default DEMO_BRAND;

Apply Brand from Config

JavaScript
import BRAND from class="code-string">'./brandConfig';

export default class BrandedApp extends LightningElement {
    connectedCallback() {
        const host = this.template.host;
        host.style.setProperty(class="code-string">'--brand-primary', BRAND.primary);
        host.style.setProperty(class="code-string">'--brand-primary-hover', BRAND.primaryHover);
        host.style.setProperty(class="code-string">'--brand-primary-contrast', BRAND.primaryContrast);
        host.style.setProperty(class="code-string">'--brand-secondary', BRAND.secondary);
        host.style.setProperty(class="code-string">'--brand-radius', BRAND.radius);
    }

    get logoUrl() {
        return BRAND.logo;
    }

    get customerName() {
        return BRAND.name;
    }
}

Extract Colors from Customer's Website

Quick steps to grab brand colors:

1
Visit customer's website
2
Open DevTools → pick element with brand color
3
Copy the hex value for primary brand color
4
Use a tool to generate hover (darken 15%) and contrast (white or black)
5
Update brandConfig.js with new values

Common Industry Brand Patterns

Dark Mode (Opt-In Enhancement)

Dark mode is not added by default. Before implementing, ask the user: "Do you want this component to support dark mode?" If yes, follow the patterns below.

Dark Token Definitions

CSS
: host {
    /* Light mode (default) */
    --brand-surface: #ffffff;
    --brand-surface-elevated: #ffffff;
    --brand-text: #181818;
    --brand-text-muted: #444444;
    --brand-border: #e5e5e5;
}

: host([data-theme=";dark"]) {
    --brand-surface: #1a1a1a;
    --brand-surface-elevated: #2d2d2d;
    --brand-text: #e5e5e5;
    --brand-text-muted: #a0a0a0;
    --brand-border: #404040;
}

System Preference Detection

JavaScript
connectedCallback() {
    this._darkModeQuery = window.matchMedia(class="code-string">'(prefers-color-scheme: dark)');
    this._handleThemeChange = (e) => {
        this.template.host.setAttribute(
            class="code-string">'data-theme',
            e.matches ? class="code-string">'dark' : class="code-string">'light'
        );
    };
    this._darkModeQuery.addEventListener(class="code-string">'change', this._handleThemeChange);

    if (this._darkModeQuery.matches) {
        this.template.host.setAttribute(class="code-string">'data-theme', class="code-string">'dark');
    }
}

disconnectedCallback() {
    this._darkModeQuery.removeEventListener(class="code-string">'change', this._handleThemeChange);
}

Manual Theme Toggle

HTML
<class="code-tag">template>
    <class="code-tag">button class="theme-toggle touch-target" onclick={handleToggleTheme}
            aria-label={themeToggleLabel}>
        <class="code-tag">lightning-icon icon-name={themeIcon} size="small"></class="code-tag">lightning-icon>
    </class="code-tag">button>
</class="code-tag">template>
JavaScript
_isDark = false;

handleToggleTheme() {
    this._isDark = !this._isDark;
    this.template.host.setAttribute(
        class="code-string">'data-theme',
        this._isDark ? class="code-string">'dark' : class="code-string">'light'
    );
}

get themeIcon() {
    return this._isDark ? class="code-string">'utility:daylight' : class="code-string">'utility:night';
}

get themeToggleLabel() {
    return this._isDark ? class="code-string">'Switch to light mode' : class="code-string">'Switch to dark mode';
}

Multi-Brand Support

For ISVs or orgs with multiple brands sharing components.

Brand Configuration Object

JavaScript
const BRANDS = {
    default: {
        primary: class="code-string">'#0176d3',
        primaryHover: class="code-string">'#014486',
        primaryContrast: class="code-string">'#ffffff',
        radius: class="code-string">'var(--slds-g-radius-border-2, 0.25rem)',
        fontWeight: class="code-string">'var(--slds-g-font-weight-6, 600)'
    },
    acme: {
        primary: class="code-string">'#e63946',
        primaryHover: class="code-string">'#c1121f',
        primaryContrast: class="code-string">'#ffffff',
        radius: class="code-string">'var(--slds-g-radius-border-4, 1rem)',
        fontWeight: class="code-string">'var(--slds-g-font-weight-7, 700)'
    },
    globex: {
        primary: class="code-string">'#2d6a4f',
        primaryHover: class="code-string">'#1b4332',
        primaryContrast: class="code-string">'#ffffff',
        radius: class="code-string">'0',
        fontWeight: class="code-string">'var(--slds-g-font-weight-5, 500)'
    }
};

Applying Brand at Runtime

JavaScript
@api brandName = class="code-string">'default';

renderedCallback() {
    const brand = BRANDS[this.brandName] || BRANDS.default;
    const host = this.template.host;
    host.style.setProperty(class="code-string">'--brand-primary', brand.primary);
    host.style.setProperty(class="code-string">'--brand-primary-hover', brand.primaryHover);
    host.style.setProperty(class="code-string">'--brand-primary-contrast', brand.primaryContrast);
    host.style.setProperty(class="code-string">'--brand-radius', brand.radius);
    host.style.setProperty(class="code-string">'--brand-font-weight', brand.fontWeight);
}

App Builder Brand Property

XML
<class="code-tag">property name="brandName" type="String" label="Brand"
          datasource="Default,Acme,Globex" default="Default" />

Experience Cloud Theme Integration

Experience Cloud sites use DXP tokens. Map brand tokens to DXP tokens for seamless theming.

CSS
: host {
    --brand-primary: var(--dxp-s-brand-1, var(--slds-g-color-accent-1, #0176d3));
    --brand-primary-contrast: var(--dxp-s-brand-1-contrast,
        var(--slds-g-color-on-accent-1, #ffffff));
    --brand-surface: var(--dxp-g-root, var(--slds-g-color-surface-1, #ffffff));
    --brand-text: var(--dxp-g-root-contrast, var(--slds-g-color-on-surface-1, #181818));
    --brand-link: var(--dxp-s-link-text-color, var(--slds-g-color-accent-1, #0176d3));
}

This way, one set of brand tokens works everywhere:

  • In Experience Cloud: picks up DXP tokens set in Experience Builder
  • In internal Lightning: falls back to SLDS hooks
  • In custom branded context: brand tokens can be overridden at the host

Component-Level Theme Overrides

Allow individual instances to override theme tokens via @api properties.

JavaScript
@api accentColor;

renderedCallback() {
    if (this.accentColor) {
        this.template.host.style.setProperty(class="code-string">'--brand-primary', this.accentColor);
    }
}
XML
<class="code-tag">property name="accentColor" type="Color" label="Accent Color" />

The Color type renders a color picker in App Builder for admin-friendly customization.

Theme Validation

Contrast Checking

When defining custom brand colors, verify contrast ratios:

Theme Test Checklist

  • [ ] All text readable on brand surface
  • [ ] Buttons visible and readable
  • [ ] Links distinguishable from body text
  • [ ] Error/warning/success states visible against brand surface
  • [ ] Dark mode renders correctly
  • [ ] Focus indicators visible against brand surface

Scoring Rubric (100 Points)Reference

Cross-Skill IntegrationReference

IndustryTypical StyleRadiusWeight
Financial ServicesConservative, navy/dark blue0 or small500 (medium)
HealthcareClean, teal/green, lots of white0.25rem400 (regular)
Tech / SaaSModern, vibrant, gradients ok0.5rem+600 (semi-bold)
RetailBold, colorful, high contrast0.25rem700 (bold)
ManufacturingIndustrial, muted, practical0500 (medium)
GovernmentMinimal, accessible, conservative0400 (regular)
PairMinimum RatioStandard
Text on surface4.5:1WCAG AA normal text
Large text on surface3:1WCAG AA large text
Button text on button bg4.5:1WCAG AA
Icon on surface3:1WCAG AA non-text
CategoryPointsPass Criteria
Token Architecture20Brand tokens above SLDS; three-level fallback chain
Dark Mode (if requested)20When requested: dark variants defined, system preference detection, manual toggle. When not requested: light mode works correctly, no hardcoded colors that would break under dark theme.
Contrast & Accessibility20All color pairs meet WCAG AA contrast ratios
Multi-Context15Works in internal Lightning, Experience Cloud, and mobile
Configurability15Brand selectable via App Builder; color picker for accents
Naming & Semantics10Tokens named by purpose, not color; consistent naming
SkillRelationship
sf-lwc-designThemes extend SLDS 2 hooks; never override them
sf-lwc-experienceDXP token integration for Experience Cloud sites
sf-lwc-stylingUtility classes consume brand tokens for branded patterns
sf-lwc-reviewAudit checks theme contrast ratios and fallback chains
sf-se-demo-scriptsQuick brand swap enables personalized demos in minutes

Navigate Look & Feel