Skip to main content

Theming Guide

ng-magary uses pure CSS variables for theming, enabling complete customization without build-time configuration or preprocessor coupling. This guide covers everything from basic color changes to creating custom themes with glassmorphism effects.

Theme System Overview

Magary’s theming system is built on three core principles:

CSS Variables

All styling uses CSS custom properties for runtime theme switching

Data Attributes

Themes are applied via data-theme attribute on <body>

Theme Service

Built-in service manages theme state and persistence

Built-in Themes

Magary includes 9 pre-designed themes:
Default light theme with cyan primary colors and glassmorphism effects.
themeService.setTheme('light');

Using the Theme Service

Basic Theme Switching

1

Inject the Theme Service

my-component.ts
import { Component, inject } from '@angular/core';
import { MagaryThemeService } from 'ng-magary';

@Component({
  selector: 'app-theme-switcher',
  standalone: true,
  template: `
    <button (click)="toggleTheme()">Toggle Theme</button>
    <p>Current theme: {{ themeService.currentTheme() }}</p>
  `,
})
export class ThemeSwitcherComponent {
  themeService = inject(MagaryThemeService);
  
  toggleTheme() {
    this.themeService.toggleTheme();
  }
}
2

Set Specific Theme

// Set theme directly
this.themeService.setTheme('dark');
this.themeService.setTheme('purple');
this.themeService.setTheme('cyberpunk');
3

Read Current Theme

// Current theme is a signal
const currentTheme = this.themeService.currentTheme();
console.log(currentTheme); // 'light', 'dark', etc.

// React to theme changes
effect(() => {
  console.log('Theme changed to:', this.themeService.currentTheme());
});

Theme Persistence

The theme service automatically persists the selected theme to localStorage and applies it on app initialization.
Storage Key: magary-theme Behavior:
  1. On first visit, respects system preference (prefers-color-scheme)
  2. Saves user selection to localStorage
  3. Restores saved theme on subsequent visits

Theme Switcher Component Example

theme-switcher.component.ts
import { Component, inject } from '@angular/core';
import { MagaryThemeService, MagaryButton, MagarySelect } from 'ng-magary';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-theme-switcher',
  standalone: true,
  imports: [CommonModule, MagaryButton, MagarySelect],
  template: `
    <div class="theme-controls">
      <h3>Theme Settings</h3>
      
      <!-- Theme Dropdown -->
      <magary-select
        [options]="themes"
        [ngModel]="themeService.currentTheme()"
        (ngModelChange)="themeService.setTheme($event)"
        placeholder="Select theme"
        optionLabel="label"
        optionValue="value"
      />
      
      <!-- Quick Toggle -->
      <magary-button
        label="Next Theme"
        icon="palette"
        (onClick)="themeService.toggleTheme()"
      />
    </div>
  `,
})
export class ThemeSwitcherComponent {
  themeService = inject(MagaryThemeService);
  
  themes = [
    { label: 'Light', value: 'light' },
    { label: 'Dark', value: 'dark' },
    { label: 'Purple', value: 'purple' },
    { label: 'Green', value: 'green' },
    { label: 'Neo', value: 'neo' },
    { label: 'Midnight', value: 'midnight' },
    { label: 'Cyberpunk', value: 'cyberpunk' },
    { label: 'Cotton', value: 'cotton' },
    { label: 'Liquid', value: 'liquid' },
  ];
}

CSS Variable Reference

Color Palette Variables

Magary uses a comprehensive color system based on CSS variables.

Primary Colors

:root {
  --primary-50: #ecfeff;
  --primary-100: #cffafe;
  --primary-200: #a5f3fc;
  --primary-300: #67e8f9;
  --primary-400: #22d3ee;
  --primary-500: #06b6d4;  /* Main primary color */
  --primary-600: #0891b2;
  --primary-700: #0e7490;
  --primary-800: #155e75;
  --primary-900: #164e63;
}
Primary colors change based on the active theme. Use --primary-500 for main actions.

Accent Colors

:root {
  --accent-50: #fefce8;
  --accent-100: #fef9c3;
  --accent-500: #eab308;  /* Main accent color */
  --accent-900: #713f12;
}

Surface Colors

:root {
  --surface-0: #ffffff;     /* Pure white background */
  --surface-50: #f8fafc;    /* Lightest gray */
  --surface-100: #f1f5f9;
  --surface-200: #e2e8f0;
  --surface-300: #cbd5e1;
  --surface-500: #64748b;
  --surface-700: #334155;
  --surface-800: #1e293b;
  --surface-900: #0f172a;   /* Darkest */
}

Text Colors

:root {
  --text-primary: #0f172a;     /* Main text */
  --text-secondary: #475569;   /* Secondary text */
  --text-tertiary: #94a3b8;    /* Disabled/placeholder */
}

Semantic Colors

:root {
  --success: #10b981;   /* Green */
  --warning: #f59e0b;   /* Orange */
  --danger: #ef4444;    /* Red */
  --info: #3b82f6;      /* Blue */
  --help: #8b5cf6;      /* Purple */
}

Layout Variables

:root {
  /* Typography */
  --font-family: "Inter", system-ui, -apple-system, sans-serif;
  
  /* Border Radius */
  --border-radius: 1.25rem;
  --magary-field-radius: 1.25rem;
  --magary-option-radius: 0.625rem;
  --magary-overlay-radius: 1.25rem;
  
  /* Spacing */
  --magary-field-padding-x: 1rem;
  --magary-overlay-padding: 0.375rem;
  --magary-overlay-gap: 0.125rem;
  
  /* Field Heights */
  --magary-field-height-sm: 2.25rem;
  --magary-field-height-md: 2.75rem;
  --magary-field-height-lg: 3.125rem;
}

Shadow Variables

:root {
  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.02);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.05);
  --shadow-lg: 0 12px 24px -4px rgba(0, 0, 0, 0.08);
  --shadow-glow: 0 0 20px rgba(6, 182, 212, 0.4);
  --magary-overlay-shadow: 0 12px 28px rgba(2, 6, 23, 0.24);
}

Glassmorphism Variables

:root {
  --glass-bg: rgba(255, 255, 255, 0.85);
  --glass-border: 1px solid rgba(219, 217, 217, 0.85);
  --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
  --backdrop-blur: blur(16px);
}
Glassmorphism variables change in dark themes to maintain proper contrast and readability.

Gradient Variables

:root {
  --gradient-primary: linear-gradient(135deg, #06b6d4 0%, #d946ef 100%);
  --gradient-hover: linear-gradient(135deg, #22d3ee 0%, #e879f9 100%);
  --gradient-text: linear-gradient(to right, #0891b2, #c026d3);
  --gradient-surface: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
}

Creating Custom Themes

Method 1: Using Data Attributes

Create a custom theme by defining CSS variables under a data-theme attribute:
styles.scss
// Custom "Ocean" theme
[data-theme='ocean'] {
  /* Primary Colors (Blue/Teal) */
  --primary-50: #e0f2fe;
  --primary-100: #bae6fd;
  --primary-200: #7dd3fc;
  --primary-300: #38bdf8;
  --primary-400: #0ea5e9;
  --primary-500: #0284c7;
  --primary-600: #0369a1;
  --primary-700: #075985;
  --primary-800: #0c4a6e;
  --primary-900: #082f49;
  
  /* Accent Colors (Coral) */
  --accent-500: #fb7185;
  
  /* Backgrounds */
  --surface-0: #f0f9ff;
  --text-primary: #082f49;
  --text-secondary: #0c4a6e;
  
  /* Glassmorphism */
  --glass-bg: rgba(240, 249, 255, 0.9);
  --glass-border: 1px solid rgba(14, 165, 233, 0.2);
  --backdrop-blur: blur(20px);
  
  /* Gradients */
  --gradient-primary: linear-gradient(135deg, #0ea5e9 0%, #fb7185 100%);
}
Then use it with the theme service:
// Add to theme service types (in your app)
type CustomTheme = Theme | 'ocean';

// Set the theme
themeService.setTheme('ocean' as any);

Method 2: Extending the Theme Service

Create a custom theme service that extends Magary’s:
custom-theme.service.ts
import { Injectable } from '@angular/core';
import { MagaryThemeService, type Theme } from 'ng-magary';

type ExtendedTheme = Theme | 'ocean' | 'sunset' | 'forest';

@Injectable({
  providedIn: 'root',
})
export class CustomThemeService extends MagaryThemeService {
  override setTheme(theme: ExtendedTheme) {
    super.setTheme(theme as Theme);
  }
  
  // Add custom theme methods
  setOceanTheme() {
    this.setTheme('ocean' as any);
  }
}

Dark Mode Implementation

Automatic System Preference Detection

Magary automatically detects system dark mode preference on first load using prefers-color-scheme media query.

Manual Dark Mode Toggle

dark-mode-toggle.component.ts
import { Component, inject } from '@angular/core';
import { MagaryThemeService, MagaryButton } from 'ng-magary';

@Component({
  selector: 'app-dark-mode-toggle',
  standalone: true,
  imports: [MagaryButton],
  template: `
    <magary-button
      [icon]="isDark() ? 'sun' : 'moon'"
      [rounded]="true"
      variant="text"
      (onClick)="toggleDarkMode()"
      [ariaLabel]="isDark() ? 'Switch to light mode' : 'Switch to dark mode'"
    />
  `,
})
export class DarkModeToggleComponent {
  themeService = inject(MagaryThemeService);
  
  isDark = () => this.themeService.currentTheme() === 'dark';
  
  toggleDarkMode() {
    const newTheme = this.isDark() ? 'light' : 'dark';
    this.themeService.setTheme(newTheme);
  }
}

Dark Theme CSS Variables

Example dark theme variable overrides:
[data-theme='dark'] {
  /* Inverted Surface Colors */
  --surface-0: #0f172a;
  --surface-50: #1e293b;
  --surface-100: #334155;
  --surface-900: #f8fafc;
  
  /* Inverted Text */
  --text-primary: #f8fafc;
  --text-secondary: #cbd5e1;
  --text-tertiary: #64748b;
  
  /* Glassmorphism for Dark */
  --glass-bg: rgba(15, 23, 42, 0.85);
  --glass-border: 1px solid rgba(100, 116, 139, 0.2);
  --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
  
  /* Adjusted Shadows */
  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 12px 24px -4px rgba(0, 0, 0, 0.5);
}

Glassmorphism Styling

Understanding Glassmorphism

Glassmorphism creates depth through:
  • Semi-transparent backgrounds
  • Backdrop blur effects
  • Subtle borders
  • Soft shadows

Using Glass Effects in Components

<magary-card
  [backgroundColor]="'var(--glass-bg)'"
  [border]="'var(--glass-border)'"
  [shadow]="3"
  [style.backdrop-filter]="'var(--backdrop-blur)'"
>
  <h2>Glassmorphism Card</h2>
  <p>Beautiful depth and transparency!</p>
</magary-card>

Custom Glass Component

glass-panel.component.scss
.glass-panel {
  background: var(--glass-bg);
  border: var(--glass-border);
  border-radius: var(--border-radius);
  padding: 2rem;
  backdrop-filter: var(--backdrop-blur);
  -webkit-backdrop-filter: var(--backdrop-blur);
  box-shadow: var(--glass-shadow);
  
  &:hover {
    background: rgba(255, 255, 255, 0.95);
    box-shadow: 0 12px 48px 0 rgba(31, 38, 135, 0.12);
  }
}

Browser Support

Backdrop Filter Support: The backdrop-filter property is supported in modern browsers but may not work in older versions. Provide fallback backgrounds.
.glass-element {
  /* Fallback for older browsers */
  background: rgba(255, 255, 255, 0.9);
  
  /* Modern browsers with backdrop-filter support */
  @supports (backdrop-filter: blur(10px)) {
    background: var(--glass-bg);
    backdrop-filter: var(--backdrop-blur);
  }
}

Advanced Theming Techniques

Per-Component Theme Overrides

<magary-card [style]="{
  '--surface-0': '#e0f2fe',
  '--text-primary': '#0c4a6e'
}">
  <p>This card has custom colors!</p>
</magary-card>

Dynamic Theme Generation

import { Component, effect, inject } from '@angular/core';
import { MagaryThemeService } from 'ng-magary';

@Component({
  selector: 'app-dynamic-theme',
  template: `...`,
})
export class DynamicThemeComponent {
  themeService = inject(MagaryThemeService);
  
  constructor() {
    effect(() => {
      const theme = this.themeService.currentTheme();
      this.applyDynamicColors(theme);
    });
  }
  
  applyDynamicColors(theme: string) {
    const root = document.documentElement;
    
    if (theme === 'custom') {
      root.style.setProperty('--primary-500', '#ff6b6b');
      root.style.setProperty('--accent-500', '#4ecdc4');
    }
  }
}

Theme Transition Animations

styles.scss
// Smooth theme transitions
:root {
  transition: 
    background-color 0.3s ease,
    color 0.3s ease;
}

* {
  transition: 
    background-color 0.3s ease,
    border-color 0.3s ease,
    color 0.3s ease;
}
Add transition-duration: 0s to rapidly changing elements to avoid performance issues.

Best Practices

Use CSS Variables

Always reference CSS variables instead of hard-coded colors for theme consistency.

Test All Themes

Verify your custom styles work across all built-in themes, especially dark mode.

Respect System Preferences

Honor user’s system dark mode preference on first visit.

Provide Contrast

Ensure sufficient color contrast ratios (WCAG AA: 4.5:1 for text).

Troubleshooting

Issue: Theme resets to default on page refresh.Solution: Ensure localStorage is enabled and not blocked:
// Check localStorage access
try {
  localStorage.setItem('test', 'test');
  localStorage.removeItem('test');
  console.log('localStorage is available');
} catch (e) {
  console.error('localStorage is blocked');
}
Issue: Glass effects appear as solid backgrounds.Solution:
  1. Check browser support for backdrop-filter
  2. Ensure parent elements don’t have overflow: hidden
  3. Verify backdrop element exists behind glass element
Issue: Custom theme colors don’t appear.Solution:
  1. Ensure data-theme attribute is set on <body>:
    document.body.setAttribute('data-theme', 'ocean');
    
  2. Check CSS selector specificity:
    // More specific
    body[data-theme='ocean'] {
      --primary-500: #0ea5e9;
    }
    
Issue: Some elements are hard to read in dark theme.Solution: Define proper dark theme variable overrides:
[data-theme='dark'] {
  --text-primary: #f8fafc;    // Light text
  --surface-0: #0f172a;       // Dark background
  
  // Invert surface colors
  --surface-900: var(--surface-50);
}

Next Steps

Component Library

Explore all components and their theming options

Styling Guide

Learn about Magary’s CSS organization and best practices

Accessibility

Ensure your custom themes meet accessibility standards

Examples

View theme examples in the live demo

Create beautiful, accessible themes that delight your users! 🎨

Build docs developers (and LLMs) love