Creating Custom Themes in Angular Applications: A Comprehensive Guide
Theming is a powerful way to customize the look and feel of Angular applications, enabling developers to create consistent, branded, and adaptable user interfaces. Whether you’re building a single-branded app or a multi-tenant platform with multiple themes, Angular provides robust tools to implement custom themes efficiently. This guide offers an in-depth exploration of creating custom themes in Angular, focusing on CSS custom properties, Angular Material theming, and dynamic theme switching. We’ll cover why theming is essential, how to set up and apply custom themes, and advanced techniques to ensure scalability and maintainability, empowering you to craft visually appealing Angular applications.
Why Create Custom Themes in Angular?
Custom themes allow Angular applications to align with brand identities, improve user experience, and adapt to different contexts. Key benefits include:
- Brand Consistency: Themes ensure colors, typography, and styles reflect your brand across the application.
- User Experience: Consistent and visually appealing designs enhance usability and engagement.
- Adaptability: Themes enable dark mode, high-contrast modes, or client-specific branding for multi-tenant apps.
- Maintainability: Centralized theme definitions simplify updates and reduce style duplication.
- Accessibility: Themes can support accessibility requirements, such as high-contrast options, as discussed in [implementing accessibility in apps](/angular/accessibility/implement-a11y-in-app).
Angular’s theming capabilities, especially when paired with Angular Material, provide a structured approach to define and apply themes, making it easier to manage styles across components. Custom themes can be static (fixed at build time) or dynamic (switchable at runtime), offering flexibility for various use cases.
Understanding Theming in Angular
Theming in Angular typically involves:
- CSS Custom Properties (CSS Variables): Allow dynamic style values that can be updated at runtime, supported by all modern browsers.
- Angular Material Theming: A built-in system for defining themes using SCSS, with pre-built components that adapt to custom color palettes and typography.
- Component Styles: Scoped styles for individual components, which can inherit or override theme properties.
- Dynamic Theming: Runtime theme switching using CSS classes or custom properties, often driven by user preferences or application state.
Angular Material’s theming system is particularly powerful, as it provides a pre-configured palette system and utilities to generate themes for Material components. For non-Material apps, CSS custom properties offer a lightweight alternative. This guide covers both approaches, ensuring you can implement theming regardless of your setup.
Setting Up Custom Theming in Angular
Before creating themes, configure your Angular project to support theming, with or without Angular Material.
Step 1: Create or Verify Your Angular Project
If you don’t have a project, create one using the Angular CLI:
ng new themed-app
cd themed-app
Ensure the Angular CLI is installed:
npm install -g @angular/cli
Step 2: Option A – Set Up Angular Material (Recommended for Material-Based Apps)
If you’re using Angular Material, install it:
ng add @angular/material
During setup, choose:
- A pre-built theme (e.g., Indigo/Pink) or a custom theme.
- Angular Material typography (optional).
- Animations (enable for smooth transitions).
This adds @angular/material and @angular/cdk to your project and imports MatButtonModule (and others) in app.module.ts. For more on Material setup, see using Angular Material for UI.
Step 3: Option B – Set Up Without Angular Material
If you’re not using Angular Material, you can use CSS custom properties for theming. No additional dependencies are required, but ensure your project uses SCSS for better style management:
ng config schematics.@schematics/angular:component.style scss
Convert existing CSS files to SCSS if needed (e.g., rename styles.css to styles.scss and update angular.json).
Step 4: Test the Setup
Run the application:
ng serve
Open http://localhost:4200 to confirm the app loads. You’re now ready to create custom themes.
Creating a Custom Theme with Angular Material
Angular Material’s theming system uses SCSS to define color palettes and typography. Let’s create a custom theme with primary, accent, and warn colors.
Step 1: Define the Theme
Create a new file src/theme.scss:
@use '@angular/material' as mat;
$my-primary: mat.define-palette(mat.$indigo-palette, 500);
$my-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$my-warn: mat.define-palette(mat.$red-palette);
$my-theme: mat.define-light-theme((
color: (
primary: $my-primary,
accent: $my-accent,
warn: $my-warn
),
typography: mat.define-typography-config(),
density: 0
));
@include mat.all-component-themes($my-theme);
Breakdown:
- Palettes: define-palette creates a color palette from Material’s predefined colors (e.g., $indigo-palette). The first argument is the palette, and the second specifies the default shade (500), with optional lighter/darker shades (A100, A400).
- Theme: define-light-theme (or define-dark-theme) configures the theme with primary, accent, and warn colors, plus typography and density.
- Apply Theme: all-component-themes applies the theme to all Material components.
Step 2: Include the Theme
Update src/styles.scss to import the theme:
@use './theme';
Ensure styles.scss is referenced in angular.json:
"styles": ["src/styles.scss"]
Step 3: Use the Theme in Components
Generate a component:
ng generate component dashboard
Edit src/app/dashboard/dashboard.component.html:
Primary Button
Accent Button
Warn Button
Dashboard
Welcome to the themed dashboard!
Update app.module.ts to import Material modules:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
@NgModule({
declarations: [AppComponent, DashboardComponent],
imports: [BrowserModule, BrowserAnimationsModule, MatButtonModule, MatCardModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Add to app.component.html:
Step 4: Test the Theme
Run the app:
ng serve
The buttons and card will reflect the custom theme (indigo primary, pink accent, red warn), applied consistently across Material components.
Creating a Custom Theme with CSS Custom Properties
For non-Material apps or lightweight theming, use CSS custom properties.
Step 1: Define the Theme
Edit src/styles.scss:
:root {
--primary-color: #3f51b5;
--accent-color: #ff4081;
--warn-color: #f44336;
--background-color: #ffffff;
--text-color: #333333;
}
body {
background: var(--background-color);
color: var(--text-color);
}
Step 2: Apply the Theme in Components
Generate a component:
ng generate component ui
Edit src/app/ui/ui.component.scss:
.button {
background: var(--primary-color);
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
&.accent {
background: var(--accent-color);
}
&.warn {
background: var(--warn-color);
}
}
.card {
background: var(--background-color);
border: 1px solid var(--primary-color);
padding: 20px;
border-radius: 8px;
}
Edit src/app/ui/ui.component.html:
Primary Button
Accent Button
Warn Button
Custom Themed Card
This card uses CSS custom properties for theming.
Add to app.component.html:
Step 3: Test the Theme
Run the app:
ng serve
The buttons and card use the defined colors, styled consistently with CSS custom properties.
Implementing Dynamic Theme Switching
Dynamic theming allows users to switch themes (e.g., light/dark mode) at runtime.
Step 1: Define Multiple Themes
For Angular Material, create multiple themes in theme.scss:
@use '@angular/material' as mat;
$light-primary: mat.define-palette(mat.$indigo-palette);
$light-accent: mat.define-palette(mat.$pink-palette);
$light-warn: mat.define-palette(mat.$red-palette);
$dark-primary: mat.define-palette(mat.$blue-grey-palette);
$dark-accent: mat.define-palette(mat.$amber-palette);
$dark-warn: mat.define-palette(mat.$deep-orange-palette);
$light-theme: mat.define-light-theme((
color: (
primary: $light-primary,
accent: $light-accent,
warn: $light-warn
)
));
$dark-theme: mat.define-dark-theme((
color: (
primary: $dark-primary,
accent: $dark-accent,
warn: $dark-warn
)
));
.light-theme {
@include mat.all-component-themes($light-theme);
}
.dark-theme {
@include mat.all-component-themes($dark-theme);
}
For CSS custom properties, update styles.scss:
:root, .light-theme {
--primary-color: #3f51b5;
--accent-color: #ff4081;
--warn-color: #f44336;
--background-color: #ffffff;
--text-color: #333333;
}
.dark-theme {
--primary-color: #90caf9;
--accent-color: #ffd740;
--warn-color: #ff5722;
--background-color: #121212;
--text-color: #ffffff;
}
body {
background: var(--background-color);
color: var(--text-color);
}
Step 2: Create a Theme Service
Generate a service:
ng generate service theme
Edit src/app/theme.service.ts:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ThemeService {
private isDarkTheme = false;
toggleTheme() {
this.isDarkTheme = !this.isDarkTheme;
this.applyTheme();
}
applyTheme() {
const theme = this.isDarkTheme ? 'dark-theme' : 'light-theme';
document.body.classList.remove('light-theme', 'dark-theme');
document.body.classList.add(theme);
}
isDarkMode(): boolean {
return this.isDarkTheme;
}
}
Step 3: Add Theme Switching to a Component
Update dashboard.component.ts (or ui.component.ts for non-Material):
import { Component } from '@angular/core';
import { ThemeService } from '../theme.service';
@Component({
selector: 'app-dashboard',
template: `
{ { themeService.isDarkMode() ? 'Switch to Light Theme' : 'Switch to Dark Theme' }}
Dashboard
Explore the themed dashboard!
`
})
export class DashboardComponent {
constructor(public themeService: ThemeService) {}
toggleTheme() {
this.themeService.toggleTheme();
}
}
For non-Material, update ui.component.html:
{ { themeService.isDarkMode() ? 'Switch to Light Theme' : 'Switch to Dark Theme' }}
Themed Card
This card adapts to the selected theme.
Update app.module.ts for non-Material:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { UiComponent } from './ui/ui.component';
@NgModule({
declarations: [AppComponent, UiComponent],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Step 4: Test Dynamic Theming
Run the app:
ng serve
Click the theme toggle button to switch between light and dark themes. Material components or custom-styled elements will update instantly, reflecting the new theme.
Accessibility Considerations
Themes must be accessible to all users:
- High Contrast: Ensure sufficient color contrast (WCAG 2.1 recommends 4.5:1 for text). Use tools like Chrome DevTools to check contrast ratios.
- Reduced Motion: Respect prefers-reduced-motion for users who prefer minimal animations, as shown in [Angular animations](/angular/ui/angular-animations).
- ARIA Attributes: Use ARIA labels for themed elements, as discussed in [using ARIA labels in UI](/angular/accessibility/use-aria-labels-in-ui).
- Readable Typography: Define accessible font sizes and weights in Material typography or custom styles.
For more, see implementing accessibility in apps.
Testing Themes
Test themes to ensure consistency and functionality:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { DashboardComponent } from './dashboard.component';
import { ThemeService } from '../theme.service';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DashboardComponent],
imports: [BrowserAnimationsModule, MatButtonModule, MatCardModule],
providers: [ThemeService]
}).compileComponents();
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should toggle theme', () => {
const themeService = TestBed.inject(ThemeService);
spyOn(themeService, 'toggleTheme').and.callThrough();
const button = fixture.nativeElement.querySelector('button');
button.click();
fixture.detectChanges();
expect(themeService.toggleTheme).toHaveBeenCalled();
expect(themeService.isDarkMode()).toBe(true);
});
});
For visual testing, use E2E tests with Cypress, as shown in creating E2E tests with Cypress. For testing setup, see using TestBed for testing.
Debugging Themes
If themes don’t apply correctly, debug with these steps:
- Verify Imports: Ensure BrowserAnimationsModule (for Material) and theme files are imported.
- Check SCSS Compilation: Confirm theme.scss is included in styles.scss and angular.json.
- Inspect CSS Variables: Use browser DevTools (F12) to check --primary-color or Material styles (e.g., background-color).
- Test Theme Switching: Log themeService.isDarkMode() to trace theme state.
- Browser Compatibility: Test in Chrome, Firefox, and Edge to rule out browser-specific issues.
For general debugging, see debugging unit tests.
Optimizing Theme Performance
To ensure themes are efficient:
- Minimize CSS: Use specific selectors to reduce CSS bloat, especially with Material’s all-component-themes.
- Lazy Load Themes: For large apps, load themes dynamically with feature modules, as shown in [creating feature modules](/angular/modules/create-feature-modules).
- Optimize Variables: Limit the number of CSS custom properties to reduce runtime overhead.
- Profile Performance: Use browser DevTools or Angular’s tools to identify style-related bottlenecks, as discussed in [profiling app performance](/angular/performance/profile-app-performance).
Integrating Theming into Your Workflow
To make theming seamless:
- Centralize Theme Definitions: Store themes in theme.scss or a dedicated directory for reusability.
- Document Themes: Comment SCSS files to explain color roles and usage.
- Automate Testing: Include theme tests in CI/CD pipelines with ng test.
- Combine with UI Libraries: Use Angular Material or other libraries for pre-themed components, as shown in [using Angular Material for UI](/angular/ui/use-angular-material-for-ui).
- Support Dark Mode: Implement dynamic theming for light/dark modes, as shown in [implementing dark mode in apps](/angular/ui/implement-dark-mode-in-app).
FAQ
What is theming in Angular?
Theming in Angular involves defining and applying consistent styles (colors, typography, etc.) across an application, using Angular Material’s SCSS-based theming or CSS custom properties, to align with brand identity and enhance user experience.
Should I use Angular Material for theming?
Angular Material is ideal for apps using Material components, offering a robust palette system and pre-themed elements. For lightweight or non-Material apps, CSS custom properties provide a flexible alternative.
How do I implement dark mode in Angular?
Use a theme service to toggle between light and dark themes, applying CSS classes or updating CSS custom properties at runtime. See the dynamic theming section or implementing dark mode in apps.
How do I test custom themes?
Use unit tests with TestBed to verify theme switching logic and component rendering. For visual testing, use E2E tests with Cypress to confirm styles, as shown in creating E2E tests with Cypress.
Conclusion
Creating custom themes in Angular applications enables developers to craft branded, adaptable, and user-friendly interfaces. Whether using Angular Material’s powerful theming system or CSS custom properties for lightweight solutions, you can define consistent styles and implement dynamic theme switching to meet diverse needs. This guide provides practical steps, from setting up themes to testing and optimizing them, ensuring your Angular apps are visually appealing and maintainable. Start integrating custom themes into your projects to deliver professional, engaging user experiences that align with your brand and accessibility standards.