Theming
Theming Overview
Egret Angular uses a powerful and flexible theming system that combines Angular Material's theming capabilities with CSS custom properties (variables) and Tailwind CSS. This approach allows for dynamic theme switching, customization, and consistent styling across the application.
Theming Architecture
The theming system in Egret Angular is built on three main components:
- Angular Material Theming: Provides the foundation for component styling.
- CSS Custom Properties: Enables dynamic theme switching without page reloads.
- Tailwind CSS Integration: Offers utility classes that respect the current theme.
Available Themes
Egret Angular comes with two pre-configured themes:
- Light Theme (egret-navy): The default theme with a light background and navy blue primary color.
- Dark Theme (egret-navy-dark): A dark theme variant with indigo as the primary color.
Light Theme Colors
Dark Theme Colors
Sidebar Themes
In addition to the main application themes, Egret Angular provides two sidebar themes that can be used independently:
- sidebar-light: A light sidebar with dark text.
- sidebar-dark: A dark sidebar with light text.
This allows you to mix and match, for example, using a light theme for the main application with a dark sidebar.
Switching Themes
Egret provides two ways to configure themes:
1. Build-time Theme Configuration
To set the default material and sidebar themes during build time, modify the setAppLayout
method in layout.service.ts
:
// In layout.service.ts
setAppLayout() {
// ********** SET DEFAULT LAYOUT **********
let defaultLayout: ILayoutConf = {
****** other options **********
sidebarColor: 'sidebar-dark', // colors are defined in color-tokens.scss
matTheme: 'egret-navy', // egret-navy, egret-navy-dark
};
}
2. Runtime Theme Switching
For runtime theme changes, use the publishLayoutChange
method from the LayoutService. This approach ensures that theme changes are properly applied and persisted to localStorage.
import { LayoutService } from 'app/shared/services/layout.service';
@Component({...})
export class YourComponent {
constructor(private layoutService: LayoutService) {}
// Switch to dark theme
switchToDarkTheme() {
this.layoutService.publishLayoutChange({
matTheme: 'egret-navy-dark'
});
}
// Switch to light theme
switchToLightTheme() {
this.layoutService.publishLayoutChange({
matTheme: 'egret-navy'
});
}
// Change sidebar theme
setSidebarTheme(theme: string) {
this.layoutService.publishLayoutChange({
sidebarColor: theme // 'sidebar-dark' or 'sidebar-light'
});
}
}
The publishLayoutChange
method automatically:
- Updates the active theme using ThemeService
- Updates the layout configuration
- Persists the theme selection to localStorage using the key defined in config.themeLocalStorageKey
- Notifies subscribers about the layout change
How Theming Works
The theming system works through several key files:
1. Color Tokens (_color-tokens.scss)
This file defines the base color palette and semantic color mappings for both light and dark themes.
// Base colors
$navy-blue: #0f174c;
$blue-default: #0081ff;
$accent-orange: #ff8a48;
$warn-red: #ff3d57;
// Light theme semantic colors
$light-theme-colors: (
color-primary: $navy-blue,
color-accent: $accent-orange,
color-warn: $warn-red,
// ...other mappings
);
// Dark theme semantic colors
$dark-theme-colors: (
color-primary: $indigo,
color-accent: $accent-orange,
color-warn: $warn-red,
// ...other mappings
);
2. CSS Variables (_css-vars.scss)
This file converts the SCSS color tokens into CSS custom properties that can be used throughout the application.
// Light theme
.egret-navy {
// Set semantic colors from light theme
@each $key, $value in colors.$light-theme-colors {
--#{$key}: #{get-rgb($value)};
}
}
// Dark theme
.egret-navy-dark {
// Dark theme semantic colors
@each $key, $value in colors.$dark-theme-colors {
--#{$key}: #{get-rgb($value)};
}
}
3. Angular Material Theme Integration (_init.scss)
This file sets up Angular Material's theming system to work with our CSS variables and defines typography.
// Define typography
$egret-typography: mat.m2-define-typography-config(
$font-family: $font-family-base,
$headline-1: mat.m2-define-typography-level(1.875rem, 2.25rem, 800, $font-family-base),
// ...other typography levels
);
// Generate static primary palette for Angular Material
$static-primary-light-palette: (
50: color.adjust($static-primary-light, $lightness: 40%),
// ...other palette entries
);
4. Tailwind Integration (tailwind.config.js)
This file extends Tailwind CSS to use our theme's CSS variables, ensuring consistency across utility classes.
module.exports = {
// Support for dark mode via the .egret-navy-dark class
darkMode: ['class', '.egret-navy-dark'],
theme: {
extend: {
colors: {
secondary: 'rgb(var(--fg-secondary))',
// Background and foreground colors
bg: {
base: 'rgb(var(--bg-base))',
card: 'rgb(var(--bg-card))',
// ...other mappings
},
// ...other color extensions
},
// ...other theme extensions
},
},
};
Theme Persistence
Egret persists the theme selection to localStorage. This is handled by the ThemeService's setActiveTheme
method:
// In theme.service.ts
private setActiveTheme(theme: ThemeConfig): void {
// Remove all existing theme classes
this.availableThemes.forEach(t => {
this.renderer.removeClass(this.documentElement, t.id);
});
// Add active theme class
this.renderer.addClass(this.documentElement, theme.id);
// Add/remove dark theme class
if (theme.mode === 'dark') {
this.renderer.addClass(this.documentElement, 'egret-navy-dark');
this.renderer.addClass(this.bodyElement, 'egret-navy-dark');
} else {
this.renderer.removeClass(this.documentElement, 'egret-navy-dark');
this.renderer.removeClass(this.bodyElement, 'egret-navy-dark');
}
// Save to localStorage
localStorage.setItem(config.themeLocalStorageKey, theme.id);
}
The theme selection is preserved between sessions, ensuring a consistent user experience.
To change themes during runtime, use the LayoutService:
// Change theme using LayoutService
layoutService.publishLayoutChange({
matTheme: 'egret-navy-dark' // or 'egret-navy' for light theme
});
Customizing Themes
To customize the existing themes or create new ones, you can modify the following files:
-
_color-tokens.scss: Modify the base colors or semantic color mappings.
// Change the primary color for light theme $light-theme-colors: ( color-primary: #1976d2, // New primary color // ...other mappings remain the same );
-
_css-vars.scss: Add new theme classes following the pattern of existing themes.
// Add a new theme .egret-purple { // Set semantic colors for the new theme @each $key, $value in $purple-theme-colors { --#{$key}: #{get-rgb($value)}; } }
-
tailwind.config.js: Update the darkMode configuration if adding new dark themes.
// Add support for multiple dark themes darkMode: ['class', '.egret-navy-dark', '.egret-purple-dark'],
After making changes to these files, you'll need to rebuild the application for the changes to take effect.
Using Theme Colors in Components
There are several ways to use theme colors in your components:
1. Using Tailwind CSS Classes
The simplest way is to use Tailwind CSS utility classes that reference theme variables:
<div class="bg-card text-base p-4 rounded-lg shadow-card">
<h2 class="text-primary font-medium">Card Title</h2>
<p class="text-secondary">Text with muted color.</p>
</div>
2. Using CSS Variables Directly
You can use CSS variables directly in your component styles:
// In component SCSS
.custom-element {
background-color: rgb(var(--bg-card));
color: rgba(var(--fg-base), 0.87);
border: 1px solid rgb(var(--color-primary));
}
3. Using Angular Material Components
Angular Material components automatically use the current theme:
<button mat-raised-button color="primary">Primary Button</button>
<button mat-raised-button color="accent">Accent Button</button>
<button mat-raised-button color="warn">Warn Button</button>
Best Practices
- Use semantic color variables instead of hardcoded color values to ensure theme consistency.
- Prefer Tailwind utility classes for styling when possible, as they automatically respect the current theme.
- Test your UI in both light and dark themes to ensure good contrast and readability.
- Use the ThemeService for programmatic theme switching rather than directly manipulating CSS classes.
- When creating custom components, follow the pattern of existing components to ensure they work with theme switching.