Web Development  

Dark Mode Implementation: Best Practices for Web Developers

A Senior Developer’s Guide to Building Dark Mode in Modern Web Applications with Angular.

Dark mode has evolved from a niche preference to an expected feature in modern web and mobile applications. In 2025, users increasingly expect interfaces that can adapt to low-light environments or personal preferences, while enterprises want consistency across products.

For senior developers, implementing dark mode is not just about flipping colors. It requires careful planning, architecture-level decisions, performance optimisation, accessibility compliance, and user experience considerations. This article explores how to implement dark mode effectively in modern web applications, with a focus on Angular-based projects and real-world best practices.

1. Why Dark Mode Matters

1.1 User Experience

Dark mode improves usability in low-light conditions, reduces eye strain, and offers a modern, visually appealing interface. Users often prefer dark mode when using devices at night or in dimly lit environments.

1.2 Energy Efficiency

On OLED and AMOLED screens, dark mode reduces power consumption because black pixels are turned off, which can extend battery life for mobile devices.

1.3 Brand and Design Consistency

Many popular applications now offer dark mode, and users expect a consistent experience across platforms. Enterprises benefit from consistent theming across their web apps, mobile apps, and internal dashboards.

1.4 Accessibility

Dark mode can enhance readability for users with visual impairments, but it must be implemented thoughtfully to maintain contrast and avoid fatigue.

2. Approaches to Dark Mode Implementation

Dark mode implementation is not one-size-fits-all. The approach depends on application architecture, frameworks, and user experience goals.

2.1 CSS Variables (Recommended)

Using CSS custom properties (variables) is the most flexible approach. You define color variables in a global stylesheet and switch them dynamically for light and dark themes.

:root {
  --primary-bg: #ffffff;
  --primary-text: #111111;
}

[data-theme="dark"] {
  --primary-bg: #121212;
  --primary-text: #e0e0e0;
}

body {
  background-color: var(--primary-bg);
  color: var(--primary-text);
}
  • Benefits: Easy to maintain, supports dynamic switching, reduces duplication

  • Works well with Angular’s global styles and component styles

2.2 Class-Based Theming

Another approach is toggling a class on the root element (e.g., dark-mode) and styling components accordingly.

body.dark-mode {
  background-color: #121212;
  color: #e0e0e0;
}
  • Simple to implement for smaller apps

  • Less scalable for large applications with many components

2.3 Component-Level Theming

For complex Angular apps, component-level theming allows individual modules to define their own dark mode styles.

Example

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.scss'],
  host: { '[class.dark-mode]': 'isDarkMode' }
})
export class CardComponent {
  @Input() isDarkMode = false;
}
  • Provides granular control

  • Useful in modular or multi-tenant applications

2.4 Third-Party Libraries

Some libraries provide out-of-the-box dark mode support:

  • Angular Material: Built-in theming with light and dark palettes

  • TailwindCSS: Supports dark: variants with a single configuration flag

  • Ngx-dark-mode: Angular utility library for theme switching

3. Dark Mode in Angular

Angular provides multiple strategies to implement dark mode effectively:

3.1 Angular Material Theming

Angular Material supports dark mode natively through predefined palettes:

import { OverlayContainer } from '@angular/cdk/overlay';
...
toggleDarkMode(isDark: boolean) {
  const overlayContainerClasses = this.overlayContainer.getContainerElement().classList;
  if (isDark) overlayContainerClasses.add('dark-theme');
  else overlayContainerClasses.remove('dark-theme');
}
  • Use SCSS theming for global and component-specific styles

  • Supports mat-toolbar, mat-card, mat-button theming out-of-the-box

3.2 Using CSS Variables in Angular

With Angular 15+ and component-scoped styles, you can implement theme switching at runtime:

@Injectable({ providedIn: 'root' })
export class ThemeService {
  setTheme(isDark: boolean) {
    document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
  }
}
  • Combine with local storage or user preferences to persist the theme across sessions

3.3 Persisting User Preferences

Use local storage, cookies, or backend user settings to remember the preferred theme:

const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);

function toggleTheme() {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
}

3.4 Automatic Dark Mode Detection

You can detect system-level dark mode preferences using media queries:

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
  • Useful for improving user experience without explicit toggles

  • Works in combination with manual overrides

4. Best Practices for Dark Mode

4.1 Avoid Pure Black Backgrounds

  • Use dark greys (#121212, #1e1e1e) instead of #000000

  • Pure black can increase eye strain and reduce readability

4.2 Maintain Proper Contrast

  • Text must maintain WCAG AA or AAA contrast ratios

  • Use tools like Contrast Checker to validate text against background

4.3 Handle Images and Icons

  • Use adaptive assets: SVGs with dynamic fill or dual light/dark versions

  • Avoid images with fixed light backgrounds that clash with dark mode

4.4 Smooth Transitions

  • Use CSS transitions for theme switching:

body {
  transition: background-color 0.3s ease, color 0.3s ease;
}
  • Improves perceived quality and user experience

4.5 Respect System Preferences

  • Offer automatic dark mode detection

  • Allow users to manually override

4.6 Accessibility Considerations

  • Ensure form inputs, focus states, and buttons are clearly visible

  • Avoid using color alone to convey information

  • Test with screen readers and high-contrast modes

5. Dark Mode Architecture in Large Applications

For enterprise-grade applications, dark mode should be treated as a first-class feature, not an afterthought.

5.1 Centralised Theme Management

  • Create a ThemeService to handle switching and persistence

  • Broadcast theme changes using RxJS BehaviorSubject:

@Injectable({ providedIn: 'root' })
export class ThemeService {
  private themeSubject = new BehaviorSubject<'light' | 'dark'>('light');
  theme$ = this.themeSubject.asObservable();

  toggleTheme() {
    const next = this.themeSubject.value === 'dark' ? 'light' : 'dark';
    this.themeSubject.next(next);
    document.documentElement.setAttribute('data-theme', next);
  }
}
  • Components can subscribe to theme$ for dynamic styling

5.2 Component-Level Theme Awareness

  • Angular components should respect the central theme state

  • Use host bindings or CSS variables to dynamically apply theme styles

5.3 Theming for Third-Party Components

  • Angular Material, PrimeNG, or Kendo UI often support dark mode, but verify component overrides

  • Custom CSS may be needed for consistent experience

6. Performance Considerations

  • Preload critical CSS variables to avoid flash of unstyled content (FOUC)

  • Minimise recalculation of styles on theme switch

  • Avoid heavy DOM manipulations during theme toggling

Example: Using CSS variables avoids recalculating every element style:

:root {
  --bg-color: #ffffff;
  --text-color: #111111;
}

[data-theme="dark"] {
  --bg-color: #121212;
  --text-color: #e0e0e0;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
}
  • Angular’s change detection does not need to rerun for theme switching

7. Testing Dark Mode

Testing is critical to ensure a polished experience.

7.1 Manual Testing

  • Check readability, contrast, and UX

  • Test in various lighting conditions and devices

7.2 Automated Visual Regression Testing

  • Use tools like Percy, Chromatic, or Cypress

  • Compare screenshots in light and dark modes for unexpected changes

7.3 Accessibility Testing

  • Validate contrast ratios

  • Test with screen readers

  • Check focus indicators, keyboard navigation, and ARIA attributes

8. Real-World Examples

8.1 Angular Material Dashboard

  • Light and dark palettes applied via OverlayContainer

  • Persistent theme using local storage

  • Dynamic component styling for charts, tables, and cards

8.2 E-Commerce Platform

  • Adaptive product images with dark/light versions

  • Dark mode toggle in user profile

  • CSS variable-based theming for performance

8.3 Marketing Websites

  • Pre-rendered dark mode via Angular Universal

  • Automatic detection of system preferences

  • Smooth transition between themes

9. Challenges in Dark Mode Implementation

  1. Legacy Components – Older UI libraries may not support dark mode

  2. Third-Party Widgets – May require overrides or custom theming

  3. Content with Fixed Colors – Images, videos, and charts may clash

  4. Performance – Switching themes on large DOM trees can cause lag

  5. Accessibility – Incorrect contrast or focus styles can harm usability

10. Future of Dark Mode

  • AI-assisted theme adaptation: Automatically adjust brightness, contrast, and colors based on environment and user preferences

  • Dynamic dark mode: Changes according to ambient light sensor

  • Cross-platform consistency: Unified theme across web, mobile, and desktop applications

  • Smart content adaptation: Images, charts, and icons automatically switch versions for dark mode

In 2025, dark mode is no longer optional; it is expected by users and enterprises alike.

Best Practices for Senior Developers

  1. Use CSS variables or Angular Material themes for maintainability

  2. Respect user preferences and system settings

  3. Ensure accessibility compliance

  4. Optimize performance to prevent FOUC and DOM recalculations

  5. Test thoroughly, including visual and accessibility testing

  6. Consider component-level and app-wide architecture for scalability

  7. Handle third-party libraries and custom assets correctly

  8. Persist user preferences using local storage or backend settings

  9. Offer smooth transitions between light and dark themes

  10. Plan for future enhancements, like AI-based or dynamic themes

Conclusion

Dark mode has evolved into a critical feature of modern web applications. Its implementation requires a combination of technical skill, thoughtful UX design, and accessibility considerations.

For Angular developers, leveraging CSS variables, Angular Material theming, signals, and component-level architecture enables maintainable, scalable, and performant dark mode implementations.

Senior developers should approach dark mode as a first-class feature, integrating it into the application architecture, CI/CD pipeline, and user experience strategy. When done correctly, dark mode not only improves usability but also elevates the overall perception of the product.

Dark mode is not just a design choice. It is a responsibility for modern web developers.