Theming
Spotlight uses CSS custom properties (--spot-* variables) for all visual styling. Because the SDK renders inside a Shadow DOM, CSS custom properties are the only CSS mechanism that crosses the shadow boundary -- making them the natural theming primitive.
How It Works
- All Spotlight component styles reference
--spot-*variables exclusively. No raw colour values, no hardcoded fonts. - Theme values are applied as inline styles on the Shadow DOM host element.
- CSS custom properties inherit through the Shadow DOM boundary, cascading to all child components.
- Switching themes swaps the property values in place -- no page reload, no re-render, approximately 0.5ms for all 34 properties.
document.body
└── <div id="spotlight-host"
style="--spot-primary: #4f46e5; --spot-bg: #ffffff; ...">
└── #shadow-root
└── components reference var(--spot-primary), var(--spot-bg), etc.Setting Theme Values
Via init() Options
Pass a flat object of CSS custom property overrides to the theme option:
Spotlight.init({
apiKey: 'spot_live_abc123',
theme: {
'--spot-primary': '#FF6B00',
'--spot-bg': '#fafafa',
'--spot-radius': '12px',
'--spot-font-family': '"Inter", system-ui, sans-serif',
},
});Values you provide override the tenant defaults from the admin panel. Properties you omit use the admin-configured or built-in defaults.
Via the Admin Panel
The admin panel includes a visual theme builder with:
- Colour pickers for each property group (primary, background, text, border, accent)
- Typography controls (font family, size scale, weight scale)
- Spacing and radius sliders
- Shadow editor
- Light/dark toggle with independent editing per variant
- Live preview panel showing a mock tooltip, modal, and banner
- "Copy from light" button for dark variant (inverts colours as a starting point)
- "Reset to defaults" per variant
Changes in the admin panel take effect immediately for all users without requiring a code deploy.
Via the API
PUT /v1/admin/themes/{theme_id}
Content-Type: application/json
{
"name": "Brand Default",
"light": {
"variables": {
"--spot-primary": "#4f46e5",
"--spot-bg": "#ffffff",
"--spot-text": "#1e293b"
}
},
"dark": {
"variables": {
"--spot-primary": "#818cf8",
"--spot-bg": "#1e1e2e",
"--spot-text": "#cdd6f4"
}
}
}CSS Custom Property Reference
Colours
| Property | Default (Light) | Default (Dark) | Description |
|---|---|---|---|
--spot-primary | #4f46e5 | #818cf8 | Primary brand colour for buttons, links, accents |
--spot-primary-hover | #4338ca | #6366f1 | Primary colour on hover |
--spot-primary-text | #ffffff | #ffffff | Text colour on primary backgrounds |
--spot-bg | #ffffff | #1e1e2e | Component background colour |
--spot-bg-secondary | #f8fafc | #2a2a3c | Secondary background (cards, code blocks) |
--spot-text | #1e293b | #cdd6f4 | Primary text colour |
--spot-text-secondary | #64748b | #a6adc8 | Secondary/muted text colour |
--spot-border | #e2e8f0 | #45475a | Border colour for cards, inputs, dividers |
--spot-success | #16a34a | #a6e3a1 | Success state colour |
--spot-warning | #d97706 | #f9e2af | Warning state colour |
--spot-error | #dc2626 | #f38ba8 | Error state colour |
--spot-overlay-bg | rgba(0, 0, 0, 0.5) | rgba(0, 0, 0, 0.7) | Backdrop overlay for modals and spotlights |
Typography
| Property | Default | Description |
|---|---|---|
--spot-font-family | system-ui, -apple-system, sans-serif | Base font family |
--spot-font-size-xs | 0.75rem | Extra small text (labels, badges) |
--spot-font-size-sm | 0.875rem | Small text (secondary content) |
--spot-font-size-base | 1rem | Base text size |
--spot-font-size-lg | 1.125rem | Large text (titles) |
--spot-font-size-xl | 1.5rem | Extra large text (modal headings) |
--spot-font-weight-normal | 400 | Normal text weight |
--spot-font-weight-medium | 500 | Medium text weight |
--spot-font-weight-bold | 600 | Bold text weight |
Spacing
| Property | Default | Description |
|---|---|---|
--spot-spacing-xs | 0.25rem | Extra small spacing |
--spot-spacing-sm | 0.5rem | Small spacing |
--spot-spacing-md | 1rem | Medium spacing (default padding) |
--spot-spacing-lg | 1.5rem | Large spacing |
Effects
| Property | Default (Light) | Default (Dark) | Description |
|---|---|---|---|
--spot-shadow | 0 1px 3px rgba(0,0,0,0.1) | 0 1px 3px rgba(0,0,0,0.4) | Default box shadow |
--spot-shadow-lg | 0 10px 25px rgba(0,0,0,0.1) | 0 10px 25px rgba(0,0,0,0.5) | Large box shadow (modals, dropdowns) |
--spot-radius | 8px | 8px | Default border radius |
--spot-radius-lg | 12px | 12px | Large border radius (modals, cards) |
Component-Specific
| Property | Default (Light) | Default (Dark) | Description |
|---|---|---|---|
--spot-hotspot-color | #4f46e5 | #818cf8 | Hotspot beacon colour |
--spot-hotspot-size | 24px | 24px | Hotspot beacon size |
--spot-progress-bg | #e2e8f0 | #45475a | Tour progress bar background |
--spot-progress-fill | #4f46e5 | #818cf8 | Tour progress bar fill colour |
--spot-z-index | 999999 | 999999 | Base z-index for the SDK host element |
TIP
Call Spotlight.getThemeVariables() at runtime to get the full list of supported properties with their current values and descriptions.
Dark Mode
Automatic Detection
The SDK listens to window.matchMedia('(prefers-color-scheme: dark)'). When a dark theme variant is configured for your tenant and the user's operating system preference is dark, the SDK automatically applies the dark variant.
// Automatic -- SDK detects OS preference
Spotlight.init({
apiKey: 'spot_live_abc123',
// No theme needed; the tenant's configured dark variant applies automatically
});Manual Control
Override automatic detection with setTheme():
// Sync with your app's theme toggle
Spotlight.setTheme('dark');
Spotlight.setTheme('light');function ThemeToggle() {
const [isDark, setIsDark] = useState(false);
const toggle = () => {
const next = !isDark;
setIsDark(next);
document.documentElement.classList.toggle('dark', next);
Spotlight.setTheme(next ? 'dark' : 'light');
};
return <button onClick={toggle}>{isDark ? 'Light' : 'Dark'}</button>;
}document.getElementById('theme-toggle').addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
Spotlight.setTheme(isDark ? 'dark' : 'light');
});Dark Mode Defaults
When no custom dark variant is configured, the SDK uses built-in dark defaults that meet WCAG AA contrast requirements:
| Contrast Pair | Ratio | WCAG AA Requirement |
|---|---|---|
Text (#cdd6f4) on Background (#1e1e2e) | 11.5:1 | 4.5:1 (passes) |
Primary (#818cf8) on Background (#1e1e2e) | 5.2:1 | 4.5:1 (passes) |
Secondary text (#a6adc8) on Background (#1e1e2e) | 7.3:1 | 4.5:1 (passes) |
Theme Switch Animation
The SDK applies smooth CSS transitions when switching between light and dark variants:
:host {
transition: color 150ms, background-color 150ms, border-color 150ms;
}Colour properties transition smoothly while layout properties (spacing, radius) change instantly to avoid layout reflow.
Creating Multiple Themes
Your tenant can have multiple named themes. Only one theme is active (flagged as default) at any time.
Common theme patterns:
| Theme Name | Use Case |
|---|---|
| Brand Default | Standard brand colours for everyday use |
| High Contrast | Accessibility-focused with maximum contrast ratios |
| Holiday | Seasonal branding for campaigns |
| Partner White-Label | Custom branding for partner-facing content |
Create and manage themes via the admin panel or the API:
# List all themes
GET /v1/admin/themes
# Create a new theme
POST /v1/admin/themes
{
"name": "High Contrast",
"light": {
"variables": {
"--spot-primary": "#0000ff",
"--spot-text": "#000000",
"--spot-bg": "#ffffff",
"--spot-border": "#000000"
}
}
}
# Set a theme as the default
PUT /v1/admin/themes/{theme_id}
{ "is_default": true }Advanced: Custom CSS Escape Hatch
If --spot-* properties do not cover your styling needs, inject custom CSS directly into the Shadow DOM:
Spotlight.init({
apiKey: 'spot_live_abc123',
customCSS: `
.spot-tooltip {
backdrop-filter: blur(8px);
}
.spot-modal__title {
background: linear-gradient(135deg, var(--spot-primary), #ff6b6b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.spot-progress-bar {
border-radius: 999px;
}
`,
});DANGER
Custom CSS targets internal class names (.spot-tooltip, .spot-modal__title, etc.) that are not a stable API. These class names may change between SDK versions without notice. Prefer --spot-* custom properties for all standard theming. Use customCSS only as a last resort.
Theme Variable Validation
All theme variable names must:
- Start with
--spot-prefix - Be recognised by the SDK (unknown variables are silently ignored)
All theme variable values are validated:
- Colour properties are checked against hex/rgb/hsl formats
- Unrecognised value formats produce a warning in debug mode but do not break rendering
Each theme variant supports a maximum of 100 custom properties. The typical theme uses 34 properties (the full default set).
Previewing Themes
The admin panel provides a live preview when editing themes. You can also preview a theme without saving it via the API:
POST /v1/admin/themes/preview
{
"light": {
"variables": {
"--spot-primary": "#FF6B00",
"--spot-radius": "16px"
}
}
}The preview endpoint returns the fully merged theme (your overrides + defaults) without persisting anything.