Theming with SCSS
What is Theming in SCSS?
Theming in SCSS refers to creating a system of variables and styles that can be easily switched or customized to change the visual appearance of a website or application. This approach allows for consistent styling across components and enables features like dark/light mode switching.
Key components of a theme system:
- Variables: Define colors, fonts, spacing, and other design tokens
- Maps: Organize related variables into structured collections
- Mixins: Create reusable patterns that apply theme variables
- Functions: Manipulate theme values programmatically
- Component Styles: Apply theme variables consistently across components
Creating a Basic Theme System
Start by defining your theme variables in a dedicated file:
// _theme-variables.scss
// Primary colors
$primary-color: #3498db;
$secondary-color: #2ecc71;
$accent-color: #e74c3c;
// Neutral colors
$text-color: #333333;
$background-color: #ffffff;
$border-color: #dddddd;
// Typography
$font-family-base: 'Roboto', sans-serif;
$font-size-base: 16px;
$line-height-base: 1.5;
// Spacing
$spacing-unit: 8px;
$spacing-small: $spacing-unit;
$spacing-medium: $spacing-unit * 2;
$spacing-large: $spacing-unit * 3;
// Border radius
$border-radius: 4px;
Then use these variables throughout your application:
// _button.scss
.button {
background-color: $primary-color;
color: $background-color;
font-family: $font-family-base;
padding: $spacing-small $spacing-medium;
border-radius: $border-radius;
border: none;
&:hover {
background-color: darken($primary-color, 10%);
}
&.secondary {
background-color: $secondary-color;
&:hover {
background-color: darken($secondary-color, 10%);
}
}
}
Advanced Theming with Maps
Using SCSS maps allows for more structured and flexible theme systems:
// _theme-maps.scss
// Define theme colors as maps
$theme-colors: (
'primary': #3498db,
'secondary': #2ecc71,
'success': #27ae60,
'danger': #e74c3c,
'warning': #f39c12,
'info': #3498db,
'light': #ecf0f1,
'dark': #2c3e50
);
// Access theme colors with a function
@function theme-color($key) {
@return map-get($theme-colors, $key);
}
// Usage
.alert-success {
background-color: theme-color('success');
color: white;
}
Creating Multiple Themes
You can define multiple themes using nested maps:
// Define multiple themes
$themes: (
'light': (
'background': #ffffff,
'text': #333333,
'border': #dddddd,
'primary': #3498db,
'secondary': #2ecc71
),
'dark': (
'background': #2c3e50,
'text': #ecf0f1,
'border': #7f8c8d,
'primary': #3498db,
'secondary': #2ecc71
)
);
// Theme mixin
@mixin themed() {
@each $theme, $map in $themes {
.theme-#{$theme} & {
$theme-map: () !global;
@each $key, $value in $map {
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
@content;
$theme-map: null !global;
}
}
}
// Access the current theme
@function t($key) {
@return map-get($theme-map, $key);
}
// Usage
.card {
@include themed() {
background-color: t('background');
color: t('text');
border: 1px solid t('border');
}
}
Implementing Dark/Light Mode
A common use case for theming is implementing dark and light modes:
1. Define theme variables
// _theme-variables.scss
// Light theme (default)
:root {
--color-background: #ffffff;
--color-text: #333333;
--color-primary: #3498db;
--color-secondary: #2ecc71;
--color-border: #dddddd;
}
// Dark theme
.dark-theme {
--color-background: #1a1a1a;
--color-text: #f5f5f5;
--color-primary: #3498db;
--color-secondary: #2ecc71;
--color-border: #444444;
}
2. Use CSS variables in your SCSS
body {
background-color: var(--color-background);
color: var(--color-text);
}
.button {
background-color: var(--color-primary);
color: white;
border: 1px solid var(--color-border);
}
3. Add JavaScript for theme switching
// theme-switcher.js
document.getElementById('theme-toggle').addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
// Optional: Save preference to localStorage
const isDarkMode = document.body.classList.contains('dark-theme');
localStorage.setItem('darkMode', isDarkMode);
});
// Check for saved theme preference
document.addEventListener('DOMContentLoaded', function() {
const isDarkMode = localStorage.getItem('darkMode') === 'true';
if (isDarkMode) {
document.body.classList.add('dark-theme');
}
});
Pro Tip: You can also respect the user's system preference using the
prefers-color-scheme
media query in your SCSS:
@media (prefers-color-scheme: dark) {
:root {
--color-background: #1a1a1a;
--color-text: #f5f5f5;
// Other dark theme variables
}
}
Best Practices for Theme Systems
- Consistent naming: Use a clear naming convention for all theme variables
- Semantic variables: Name variables by their purpose, not their value (e.g.,
$primary-color
not$blue
) - Layer your variables: Create base tokens and derived tokens
- Document your theme: Create a style guide showing all theme components
- Test all themes: Ensure all UI components work in all theme variations
- Consider accessibility: Ensure sufficient contrast in all themes
Example: Layered Variables
// Base tokens (primitive values)
$color-blue-500: #3498db;
$color-green-500: #2ecc71;
$color-gray-100: #f5f5f5;
$color-gray-900: #1a1a1a;
// Semantic tokens (usage-based)
$color-primary: $color-blue-500;
$color-secondary: $color-green-500;
$color-background-light: $color-gray-100;
$color-background-dark: $color-gray-900;
// Component tokens (component-specific)
$button-background: $color-primary;
$button-text: white;
$card-background: $color-background-light;