Experte152025-01-15

SCSS Dark Mode - Theme Switching

Implementiere professionelles Dark Mode Theming mit SCSS. CSS Variables, prefers-color-scheme und Theme Toggle.

#scss#dark-mode#themes#css-variables

SCSS Dark Mode - Theme Switching

Lerne wie du professionelles Dark Mode mit SCSS implementierst.

Strategie 1: CSS Custom Properties

Variables definieren

// _themes.scss
:root {
  // Light Theme (Default)
  --color-background: #ffffff;
  --color-surface: #f5f5f5;
  --color-text-primary: #1a1a1a;
  --color-text-secondary: #666666;
  --color-border: #e0e0e0;
  --color-primary: #007bff;
  --color-primary-hover: #0056b3;
}

[data-theme="dark"] {
  // Dark Theme
  --color-background: #1a1a1a;
  --color-surface: #2d2d2d;
  --color-text-primary: #ffffff;
  --color-text-secondary: #b0b0b0;
  --color-border: #404040;
  --color-primary: #4d9fff;
  --color-primary-hover: #7db3ff;
}

Usage in Components

.card {
  background: var(--color-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  padding: 1rem;
  border-radius: 8px;

  &__title {
    color: var(--color-text-primary);
  }

  &__description {
    color: var(--color-text-secondary);
  }
}

.button {
  background: var(--color-primary);
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;

  &:hover {
    background: var(--color-primary-hover);
  }
}

Strategie 2: SCSS Mixins

// _themes-mixin.scss
@mixin light-theme {
  --color-background: #ffffff;
  --color-text: #000000;
}

@mixin dark-theme {
  --color-background: #1a1a1a;
  --color-text: #ffffff;
}

// Apply
:root {
  @include light-theme;
}

[data-theme="dark"] {
  @include dark-theme;
}

Strategie 3: SCSS Maps

// _color-palette.scss
$themes: (
  light: (
    background: #ffffff,
    surface: #f5f5f5,
    text-primary: #1a1a1a,
    text-secondary: #666666,
    border: #e0e0e0,
    primary: #007bff,
    success: #28a745,
    warning: #ffc107,
    error: #dc3545
  ),
  dark: (
    background: #1a1a1a,
    surface: #2d2d2d,
    text-primary: #ffffff,
    text-secondary: #b0b0b0,
    border: #404040,
    primary: #4d9fff,
    success: #4caf50,
    warning: #ffb300,
    error: #f44336
  )
);

// Function to get theme color
@function theme-color($theme, $color) {
  @return map-get(map-get($themes, $theme), $color);
}

// Generate CSS Variables
:root {
  @each $color, $value in map-get($themes, light) {
    --color-#{$color}: #{$value};
  }
}

[data-theme="dark"] {
  @each $color, $value in map-get($themes, dark) {
    --color-#{$color}: #{$value};
  }
}

prefers-color-scheme

Automatische Erkennung der System-Präferenz:

// Default: Light Theme
:root {
  --color-background: #ffffff;
  --color-text: #000000;
}

// Dark Theme wenn System Preference
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-background: #1a1a1a;
    --color-text: #ffffff;
  }
}

// Explizite Theme-Auswahl überschreibt System
[data-theme="light"] {
  --color-background: #ffffff;
  --color-text: #000000;
}

[data-theme="dark"] {
  --color-background: #1a1a1a;
  --color-text: #ffffff;
}

Complete Theme System

Professional Dark Mode Implementation
// abstracts/_themes.scss
$color-schemes: (
  light: (
    // Backgrounds
    bg-primary: #ffffff,
    bg-secondary: #f8f9fa,
    bg-tertiary: #e9ecef,

    // Text
    text-primary: #212529,
    text-secondary: #6c757d,
    text-tertiary: #adb5bd,
    text-inverse: #ffffff,

    // Borders
    border-light: #e9ecef,
    border-medium: #dee2e6,
    border-dark: #ced4da,

    // Brand
    primary: #0d6efd,
    primary-hover: #0b5ed7,
    success: #198754,
    warning: #ffc107,
    error: #dc3545,
    info: #0dcaf0,

    // Shadows
    shadow-sm: rgba(0, 0, 0, 0.075),
    shadow-md: rgba(0, 0, 0, 0.15),
    shadow-lg: rgba(0, 0, 0, 0.25)
  ),
  dark: (
    // Backgrounds
    bg-primary: #212529,
    bg-secondary: #2b3035,
    bg-tertiary: #343a40,

    // Text
    text-primary: #f8f9fa,
    text-secondary: #adb5bd,
    text-tertiary: #6c757d,
    text-inverse: #212529,

    // Borders
    border-light: #495057,
    border-medium: #343a40,
    border-dark: #2b3035,

    // Brand
    primary: #0d6efd,
    primary-hover: #3d8bfd,
    success: #198754,
    warning: #ffc107,
    error: #dc3545,
    info: #0dcaf0,

    // Shadows
    shadow-sm: rgba(0, 0, 0, 0.3),
    shadow-md: rgba(0, 0, 0, 0.5),
    shadow-lg: rgba(0, 0, 0, 0.7)
  )
);

// Generate CSS Variables
@mixin generate-theme($theme-name) {
  $theme: map-get($color-schemes, $theme-name);

  @each $key, $value in $theme {
    --#{$key}: #{$value};
  }
}

// Apply Themes
:root {
  @include generate-theme(light);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    @include generate-theme(dark);
  }
}

[data-theme="light"] {
  @include generate-theme(light);
}

[data-theme="dark"] {
  @include generate-theme(dark);
}

// Usage in Components
.card {
  background: var(--bg-primary);
  color: var(--text-primary);
  border: 1px solid var(--border-light);
  box-shadow: 0 2px 4px var(--shadow-sm);

  &:hover {
    box-shadow: 0 4px 8px var(--shadow-md);
  }
}

.button {
  background: var(--primary);
  color: var(--text-inverse);

  &:hover {
    background: var(--primary-hover);
  }
}

JavaScript Toggle

// theme-toggle.js
class ThemeToggle {
  constructor() {
    this.theme = this.getTheme()
    this.applyTheme(this.theme)
  }

  getTheme() {
    // 1. Check localStorage
    const stored = localStorage.getItem('theme')
    if (stored) return stored

    // 2. Check system preference
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      return 'dark'
    }

    return 'light'
  }

  applyTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme)
    localStorage.setItem('theme', theme)
    this.theme = theme
  }

  toggle() {
    const newTheme = this.theme === 'light' ? 'dark' : 'light'
    this.applyTheme(newTheme)
  }
}

// Usage
const themeToggle = new ThemeToggle()

document.getElementById('theme-toggle')?.addEventListener('click', () => {
  themeToggle.toggle()
})

Transition Between Themes

// Smooth transitions
* {
  transition-property: background-color, border-color, color, fill, stroke;
  transition-duration: 200ms;
  transition-timing-function: ease-in-out;
}

// Disable transitions on page load
.no-transition * {
  transition: none !important;
}
// Remove no-transition class after load
window.addEventListener('DOMContentLoaded', () => {
  setTimeout(() => {
    document.body.classList.remove('no-transition')
  }, 100)
})

Theme Toggle Button

.theme-toggle {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: var(--primary);
  border: none;
  cursor: pointer;
  box-shadow: 0 4px 8px var(--shadow-md);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 1.5rem;
  transition: all 0.3s ease;

  &:hover {
    transform: scale(1.1);
    box-shadow: 0 6px 12px var(--shadow-lg);
  }

  // Icons
  .icon-sun {
    display: none;
  }

  .icon-moon {
    display: block;
  }

  [data-theme="dark"] & {
    .icon-sun {
      display: block;
    }

    .icon-moon {
      display: none;
    }
  }
}

📝 Quiz

Welche Methode ist am flexibelsten für Dark Mode?

Tipps & Tricks

System Preference Detection

// Listen to system preference changes
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
      themeToggle.applyTheme(e.matches ? 'dark' : 'light')
    }
  })

Theme-Aware Images

.logo {
  content: url('/logo-light.svg');

  [data-theme="dark"] & {
    content: url('/logo-dark.svg');
  }
}

Syntax Highlighting Themes

.code-block {
  // Light Theme
  background: #f6f8fa;
  color: #24292e;

  [data-theme="dark"] & {
    background: #1e1e1e;
    color: #d4d4d4;
  }
}

Häufige Fehler

Fehler 1: Vergessene Transitions

PROBLEM:

// Harter Wechsel zwischen Themes
.card {
  background: var(--bg-primary);
}

LÖSUNG:

.card {
  background: var(--bg-primary);
  transition: background-color 200ms ease;
}

Fehler 2: Fehlende Fallbacks

PROBLEM:

color: var(--text-primary);
// Keine Fallback wenn Variable fehlt

LÖSUNG:

color: var(--text-primary, #000000);

Fehler 3: Flash of Unstyled Content

PROBLEM:

// Theme wird nach Page Load gesetzt
window.addEventListener('load', () => {
  applyTheme()
})

LÖSUNG:

<script>
  // Inline im <head> - vor dem Rendering
  const theme = localStorage.getItem('theme') || 'light'
  document.documentElement.setAttribute('data-theme', theme)
</script>
🎯

Zusammenfassung

Du hast gelernt:

  • ✅ CSS Custom Properties für Themes
  • ✅ prefers-color-scheme Media Query
  • ✅ SCSS Maps für Theme-Management
  • ✅ JavaScript Theme Toggle
  • ✅ Smooth Transitions
  • ✅ System Preference Detection

Key Takeaways:

  • CSS Variables > separate Stylesheets
  • prefers-color-scheme für Auto-Detection
  • localStorage für User Preference
  • Transitions für smooth Wechsel
  • Inline Script gegen FOUC

Viel Erfolg mit Dark Mode! 🌙

SCSS/SASSLektion 12 von 13
92% abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten