Command Palette

Search for a command to run...

Theme Manager

Create and manage multiple color themes for your animations using the Theme Manager. Build light and dark modes, brand variations, and custom color schemes that can be switched at runtime without re-exporting.

What is Theme Manager?

Interface for creating and managing multiple color themes.

Theme Manager Overview

Theme Manager:

  • Centralized interface for theme creation
  • Define multiple themes (light, dark, brand variants)
  • Assign color values to slots per theme
  • Preview themes in real-time
  • Apply themes to animation
  • Export with multiple themes

Core Functions:

  • Create Themes: Add new theme variations
  • Edit Theme Colors: Define color slot values for each theme
  • Manage Themes: Rename, duplicate, delete themes
  • Apply Themes: Switch active theme in editor
  • Preview Changes: See updates in real-time
  • Export Themes: Package multiple themes in dotLottie

Benefits of Theme Manager

Advantages:

  • Visual Interface: Easy theme creation without code
  • Real-time Preview: See changes immediately on canvas
  • Multi-theme Support: Create unlimited theme variations
  • Organized Workflow: All themes managed in one place
  • Export Ready: Themes automatically included in dotLottie export
  • Professional Results: Consistent, well-structured themes

Use Cases:

  • Light and dark mode creation
  • Brand color variations
  • Seasonal theme variations
  • Client-specific themes
  • A/B testing color schemes
  • Platform-specific themes (iOS, Android, Web)

Accessing Theme Manager

Multiple ways to open Theme Manager.

Opening Theme Manager

Method 1 - Left Sidebar:

  1. Look for "Theme Manager" or "Themes" in left sidebar

  2. Click "Theme Manager" button

  3. Panel opens showing theme list

  4. View and manage themes

Method 2 - Menu Bar:

  1. Click Window or View menu

  2. Select "Theme Manager"

  3. Panel opens

  4. Start creating themes

Method 3 - Panel Switcher:

  1. Look for panel tabs/switcher

  2. Find "Themes" or "Theme Manager" tab

  3. Click to switch to theme panel

  4. Panel displays

Method 4 - Properties Panel:

  1. Select color property linked to slot

  2. Look for "Manage Themes" link or button

  3. Click to open Theme Manager

  4. Opens with relevant slot focused

Theme Manager Interface

Panel Sections:

  • Theme List: All created themes
  • Active Theme Indicator: Currently applied theme
  • Create Theme Button: Add new theme
  • Theme Editor: Edit selected theme
  • Color Slot List: All color slots in project
  • Slot Value Editor: Set colors per theme
  • Preview Area: See theme applied (or use canvas)
  • Apply/Export Options: Use theme, export settings

Visual Indicators:

  • Active theme highlighted or marked
  • Slot colors displayed as swatches
  • Undefined slots indicated (warnings)
  • Changes preview in real-time

Creating Themes

Add new theme variations to your animation.

How to Create a Theme

Creating a New Theme:

  1. Open Theme Manager

  2. Click "Create Theme" or "New Theme" button

  3. Theme creation dialog opens

  4. Enter theme name (e.g., "Light", "Dark", "Brand A")

  5. Choose base theme (optional - copies existing theme colors)

  6. Click "Create" or "Add"

  7. New theme appears in theme list

  8. Ready to define colors

Theme Naming:

  • Use descriptive names: "Light Mode", "Dark Mode", "Summer Theme"
  • Indicate purpose or context: "iOS Theme", "Android Theme"
  • Avoid generic names: "Theme 1", "New Theme"
  • Consistent naming convention

Common Theme Names:

  • Light - Light mode/theme
  • Dark - Dark mode/theme
  • Brand Primary - Main brand colors
  • Brand Secondary - Alternative brand colors
  • High Contrast - Accessibility theme
  • Seasonal - Holiday or seasonal variations

Default Theme

First Theme:

  • First theme created becomes default
  • Or existing theme marked as default
  • Default theme used when no theme specified
  • Can change default designation

Default Theme Purpose:

  • Fallback when theme not specified
  • Used in players without theme support
  • Preview default in editor
  • Export default for compatibility

Duplicating Themes

Copy Existing Theme:

  1. Select theme to duplicate

  2. Right-click or click "Duplicate" button

  3. New theme created with same slot values

  4. Rename duplicated theme

  5. Modify colors as needed

Duplication Benefits:

  • Start from existing theme
  • Faster than defining from scratch
  • Maintain similar color relationships
  • Create variations efficiently

Use Cases:

  • Create "Dark" from "Light" theme
  • Create seasonal variations
  • Create subtle brand variants
  • Create A/B test variations

Editing Theme Colors

Define color values for each theme.

Theme Editor Interface

Editing a Theme:

  1. Select theme from theme list

  2. Theme editor displays

  3. Shows all color slots

  4. Current value for each slot in this theme

  5. Color swatches clickable

What You See:

  • List of all color slots in project
  • Current color value for each slot in selected theme
  • Color swatches (visual preview)
  • Slot names
  • Undefined slots (if any)

Assigning Colors to Slots

Set Slot Color for Theme:

  1. Select theme to edit

  2. Find color slot in list

  3. Click color swatch for that slot

  4. Color picker opens

  5. Choose color for this theme

  6. Color updates immediately

  7. Preview on canvas updates (if theme active)

Color Selection:

  • Use color picker (HEX, RGB, HSL)
  • Pick from canvas (eyedropper, if available)
  • Enter hex code directly
  • Use document colors
  • Copy/paste colors between slots

Defining All Slots

Complete Theme Definition:

  • All slots should be defined for each theme
  • Undefined slots may cause issues
  • Theme Manager warns about undefined slots
  • Define all slots before exporting

Workflow:

  1. Create theme

  2. Go through each slot

  3. Assign appropriate color

  4. Verify all slots defined

  5. Preview theme

  6. Adjust colors as needed

Undefined Slot Handling:

  • Warning indicator in Theme Manager
  • May fall back to default color
  • May cause visual inconsistency
  • Always define all slots per theme

Light and Dark Themes

Creating light and dark mode support.

Light Theme Setup

Light Theme Colors:

  1. Create theme named "Light"

  2. Define slot colors for light mode:

    • Background: White or light gray (#FFFFFF, #FAFAFA)
    • Surface: Light gray or white (#F5F5F5, #FFFFFF)
    • Primary: Vibrant brand color (#2196F3)
    • Text: Dark gray or black (#212121, #000000)
    • Text Secondary: Medium gray (#757575)
    • Border: Light gray (#E0E0E0)
  3. Preview on canvas

  4. Adjust for readability

  5. Verify contrast ratios

Light Theme Characteristics:

  • Bright backgrounds
  • Dark text on light backgrounds
  • High contrast for readability
  • Vibrant accent colors
  • Subtle shadows and borders

Dark Theme Setup

Dark Theme Colors:

  1. Create theme named "Dark"

  2. Define slot colors for dark mode:

    • Background: Dark gray or black (#121212, #000000)
    • Surface: Slightly lighter gray (#1E1E1E, #2C2C2C)
    • Primary: Lighter, desaturated brand color (#64B5F6)
    • Text: White or light gray (#FFFFFF, #E0E0E0)
    • Text Secondary: Medium gray (#B0B0B0)
    • Border: Dark gray (#424242)
  3. Preview on canvas

  4. Adjust for readability

  5. Verify contrast ratios

Dark Theme Characteristics:

  • Dark backgrounds (not pure black, typically #121212)
  • Light text on dark backgrounds
  • Slightly desaturated colors (less vibrant than light mode)
  • Elevated surfaces slightly lighter than background
  • Reduced contrast to avoid eye strain

Color Relationships

Maintaining Harmony:

  • Primary color should be adjusted between themes
  • Light theme: Vibrant colors
  • Dark theme: Desaturated, lighter colors
  • Text must meet WCAG contrast ratios (4.5:1 minimum)
  • Surface colors should have clear hierarchy

Example Color Mapping:

Slot: Primary
- Light Theme: #2196F3 (vivid blue)
- Dark Theme: #64B5F6 (lighter, desaturated blue)

Slot: Background
- Light Theme: #FFFFFF (white)
- Dark Theme: #121212 (dark gray)

Slot: Text
- Light Theme: #212121 (dark gray)
- Dark Theme: #FFFFFF (white)

Slot: Surface
- Light Theme: #F5F5F5 (light gray, darker than background)
- Dark Theme: #1E1E1E (lighter than background for elevation)

Applying Themes

Switch between themes in the editor.

Applying a Theme

How to Apply:

  1. Open Theme Manager

  2. Select theme from theme list

  3. Click "Apply" or "Set Active" button

  4. Theme becomes active

  5. Canvas updates with theme colors

  6. All slots use values from active theme

Active Theme Indicator:

  • Active theme highlighted in list
  • Badge or checkmark indicator
  • Name displayed prominently
  • Canvas reflects active theme

Previewing Themes

Real-time Preview:

  • Changes to theme colors update immediately
  • Switch themes to see differences
  • Canvas updates in real-time
  • Compare themes side-by-side (if supported)

Preview Workflow:

  1. Apply Light theme

  2. Review animation appearance

  3. Apply Dark theme

  4. Compare differences

  5. Adjust colors as needed

  6. Switch back and forth

  7. Refine until satisfied

Theme Switching

Switching Between Themes:

  • Click theme in Theme Manager to activate
  • Or use dropdown/selector (if available)
  • Canvas updates immediately
  • All color slots update to theme values
  • Animation plays with new colors

Use Cases:

  • Preview how animation looks in each theme
  • Compare color choices
  • Verify theme quality
  • Test contrast and readability
  • Ensure smooth transitions

Managing Multiple Themes

Organize and maintain theme variations.

Theme List Management

Theme Organization:

  • Themes listed in Theme Manager
  • Rename themes for clarity
  • Reorder themes (if supported)
  • Delete unused themes
  • Duplicate for variations

Renaming Themes:

  1. Select theme

  2. Right-click or click rename button

  3. Enter new name

  4. Press Enter to confirm

  5. Updated name appears in list

Deleting Themes:

  1. Select theme to delete

  2. Click "Delete" button or press Delete key

  3. Confirmation dialog appears (typically)

  4. Confirm deletion

  5. Theme removed from list

  6. Cannot delete if only one theme remains (typically)

Theme Count Considerations

How Many Themes?

  • Typical: 2-3 themes (light, dark, and maybe brand variant)
  • Simple Projects: 1-2 themes
  • Complex Projects: 3-5 themes
  • Too Many: Hard to maintain, confusing for users

When to Add Theme:

  • Clear use case (light/dark mode)
  • Brand variations needed
  • Platform-specific requirements
  • Seasonal or event variations
  • User customization options

When Not to Add:

  • Minor color tweaks (use one theme)
  • Overly similar variations
  • No clear use case
  • Maintenance burden

Brand Variation Themes

Creating multiple brand color schemes.

Creating Brand Themes

Brand Variation Workflow:

  1. Create color slots for brand colors

  2. Create first brand theme (e.g., "Brand A")

  3. Define brand A color values

  4. Duplicate theme for variant

  5. Rename to "Brand B"

  6. Change primary brand colors

  7. Adjust related colors

  8. Preview both themes

Brand Theme Example:

Slots:
- Primary (main brand color)
- Secondary (accent color)
- Background
- Text

Brand A Theme:
- Primary: #2196F3 (blue)
- Secondary: #FF5722 (orange)
- Background: #FFFFFF
- Text: #212121

Brand B Theme:
- Primary: #9C27B0 (purple)
- Secondary: #4CAF50 (green)
- Background: #FFFFFF
- Text: #212121

White-Label Themes

Multi-Client Support:

  • Create theme per client
  • Each theme has client's brand colors
  • Same animation, different branding
  • Export with all client themes
  • Select theme at runtime based on client

Use Cases:

  • SaaS products with multiple clients
  • White-label applications
  • Partner integrations
  • Reseller products
  • Multi-brand companies

Theme Workflows

Common patterns for theme creation.

Light/Dark Mode Workflow

Complete Light/Dark Setup:

  1. Plan color slots:

    • Identify all themeable colors
    • Create slots: Primary, Background, Surface, Text, Text Secondary, Border, etc.
  2. Link properties:

    • Select all color properties
    • Link to appropriate slots
    • Verify all themed properties linked
  3. Create Light theme:

    • Create "Light" theme
    • Define all slot values for light mode
    • Bright backgrounds, dark text, vibrant colors
  4. Create Dark theme:

    • Duplicate Light theme or create new
    • Rename to "Dark"
    • Define all slot values for dark mode
    • Dark backgrounds, light text, desaturated colors
  5. Preview both themes:

    • Switch between Light and Dark
    • Verify readability and contrast
    • Adjust colors as needed
  6. Export with themes:

    • Export as dotLottie
    • Both themes embedded
    • Ready for runtime switching

Brand Variation Workflow

Multiple Brand Themes:

  1. Create brand color slots:

    • Primary, Secondary brand colors
    • Supporting colors (background, text)
  2. Link brand elements:

    • Logo colors
    • Button colors
    • Accent elements
  3. Create first brand theme:

    • "Brand A" or client name
    • Define all brand colors
  4. Create additional brand themes:

    • Duplicate or create new
    • Define colors for each brand
    • Maintain consistency in non-brand colors
  5. Preview each brand:

    • Switch between brands
    • Verify each looks professional
  6. Export with all brands:

    • Export as dotLottie
    • All brand themes included
    • Select brand at runtime

A/B Testing Workflow

Test Color Variations:

  1. Create base theme

  2. Create variation theme

  3. Change specific colors to test

  4. Export with both themes

  5. Use in A/B test

  6. Select theme based on test group

  7. Measure results

  8. Keep winning theme

Advanced Theme Management

Advanced techniques for theme creation.

Theme Inheritance

Base Theme Concept:

  • Create base theme with most colors defined
  • Duplicate for variations
  • Only change specific colors
  • Maintains consistency across themes

Benefits:

  • Faster theme creation
  • Consistent color relationships
  • Easy maintenance
  • Clear relationships between themes

Seasonal Themes

Holiday and Seasonal Variations:

  1. Create base year-round theme

  2. Duplicate for seasonal variation

  3. Adjust colors for season/holiday

  4. Preview seasonal version

  5. Export with both themes

  6. Switch based on date/time

Seasonal Theme Examples:

  • Winter/Holiday: Cool colors, snow effects
  • Summer: Warm, bright colors
  • Spring: Pastels, fresh colors
  • Fall: Warm, earthy tones
  • Halloween: Orange and black
  • Christmas: Red and green

Dynamic Theme Selection

Runtime Theme Logic:

// Detect user's system theme
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = prefersDark ? "dark" : "light";

// Load animation with detected theme
player.loadAnimation({
  path: "animation.lottie",
  theme: theme,
});

// Listen for theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
  const newTheme = e.matches ? "dark" : "light";
  player.setTheme(newTheme);
});

Theme Selection Strategies:

  • System preference: Detect user's OS theme
  • User setting: Allow manual theme selection
  • Time-based: Auto-switch based on time of day
  • Context-based: Theme based on app state or page
  • Brand-based: Theme based on client/brand context

Under the Hood: Theme State Management

Deep dive into how Lottie Creator manages theme state and operations.

Theme State Machine Architecture

Theme management uses a state machine (XState) to handle theme operations and state transitions.

State Machine Overview:

Theme State Machine
├── States
│   ├── idle          - No operation in progress
│   ├── creating      - Creating new theme
│   ├── editing       - Editing theme colors
│   ├── applying      - Switching active theme
│   ├── deleting      - Deleting theme
│   └── validating    - Validating theme completeness
├── Events
│   ├── CREATE_THEME       - Trigger theme creation
│   ├── EDIT_THEME         - Enter edit mode
│   ├── APPLY_THEME        - Switch active theme
│   ├── DELETE_THEME       - Remove theme
│   ├── UPDATE_SLOT_COLOR  - Change slot color in theme
│   └── VALIDATE           - Check theme completeness
└── Context
    ├── themes            - Array of all themes
    ├── activeThemeId     - Currently active theme
    ├── editingThemeId    - Theme being edited (if any)
    └── validationErrors  - Validation issues

State Machine Implementation:

import { createMachine, assign } from "xstate";

const themeManagerMachine = createMachine({
  id: "themeManager",
  initial: "idle",
  context: {
    themes: [],
    activeThemeId: null,
    editingThemeId: null,
    validationErrors: [],
  },
  states: {
    idle: {
      on: {
        CREATE_THEME: {
          target: "creating",
          actions: "prepareThemeCreation",
        },
        EDIT_THEME: {
          target: "editing",
          actions: "setEditingTheme",
        },
        APPLY_THEME: {
          target: "applying",
          actions: "prepareThemeApplication",
        },
        DELETE_THEME: {
          target: "deleting",
          actions: "prepareThemeDeletion",
        },
      },
    },
    creating: {
      entry: "createNewTheme",
      on: {
        SUCCESS: {
          target: "idle",
          actions: "addThemeToList",
        },
        ERROR: {
          target: "idle",
          actions: "handleCreationError",
        },
      },
    },
    editing: {
      on: {
        UPDATE_SLOT_COLOR: {
          actions: "updateSlotColorInTheme",
        },
        FINISH_EDITING: {
          target: "idle",
          actions: "finalizeEditing",
        },
      },
    },
    applying: {
      entry: "applyThemeToScene",
      on: {
        SUCCESS: {
          target: "idle",
          actions: ["setActiveTheme", "updateCanvas"],
        },
        ERROR: {
          target: "idle",
          actions: "handleApplicationError",
        },
      },
    },
    deleting: {
      entry: "deleteTheme",
      on: {
        SUCCESS: {
          target: "idle",
          actions: "removeThemeFromList",
        },
        ERROR: {
          target: "idle",
          actions: "handleDeletionError",
        },
      },
    },
    validating: {
      entry: "validateThemes",
      on: {
        SUCCESS: {
          target: "idle",
          actions: "clearValidationErrors",
        },
        ERROR: {
          target: "idle",
          actions: "setValidationErrors",
        },
      },
    },
  },
});

State Transitions:

// Create theme
themeManager.send({
  type: "CREATE_THEME",
  data: {
    name: "Dark Mode",
    basedOn: "light", // Optional: duplicate existing theme
  },
});

// Edit theme color
themeManager.send({
  type: "UPDATE_SLOT_COLOR",
  data: {
    themeId: "dark",
    slotId: "primary-color",
    color: { r: 100, g: 181, b: 246, a: 1 },
  },
});

// Apply theme
themeManager.send({
  type: "APPLY_THEME",
  data: {
    themeId: "dark",
  },
});

DEFAULT_THEME Concept

Every project has a DEFAULT_THEME for compatibility and fallback.

What is DEFAULT_THEME?

  • Special Theme ID: Reserved identifier "DEFAULT_THEME"
  • Always Exists: Created automatically with first color slot
  • Fallback Mechanism: Used when no theme specified
  • Compatibility: Ensures animations work in non-theme-aware players
  • Initial Values: Slots created with DEFAULT_THEME color

DEFAULT_THEME Structure:

const DEFAULT_THEME = {
  id: "DEFAULT_THEME",
  name: "Default",
  isSystem: true, // System-generated, can't be deleted
  isPrimary: false, // Not a user-created primary theme
  colors: new Map([
    ["primary-color", { r: 33, g: 150, b: 243, a: 1 }],
    ["bg-color", { r: 255, g: 255, b: 255, a: 1 }],
    // ... all slot default colors
  ]),
};

DEFAULT_THEME Behavior:

class ThemeManager {
  getActiveTheme(): Theme {
    // If no theme explicitly set, use DEFAULT_THEME
    if (!this.activeThemeId) {
      return this.themes.get("DEFAULT_THEME");
    }
    return this.themes.get(this.activeThemeId);
  }

  getSlotColor(slotId: string): Color {
    const activeTheme = this.getActiveTheme();

    // Try to get color from active theme
    if (activeTheme.hasColor(slotId)) {
      return activeTheme.getColor(slotId);
    }

    // Fallback to DEFAULT_THEME
    const defaultTheme = this.themes.get("DEFAULT_THEME");
    return defaultTheme.getColor(slotId);
  }

  createColorSlot(slotId: string, initialColor: Color): void {
    const slot = new ColorSlot(slotId, initialColor);

    // Automatically add slot to DEFAULT_THEME
    const defaultTheme = this.themes.get("DEFAULT_THEME");
    defaultTheme.setColor(slotId, initialColor);

    // Also add to all other themes (prompts user for colors)
    for (const theme of this.themes.values()) {
      if (theme.id !== "DEFAULT_THEME") {
        theme.setColor(slotId, initialColor); // Use default as starting point
      }
    }
  }
}

Why DEFAULT_THEME Exists:

  • Color Slot Creation: New slots need initial color values
  • Fallback: When theme missing or incomplete
  • Editor Mode: Working without explicit theme selection
  • Backwards Compatibility: Projects from before theming
  • Export Safety: Ensures all slots have values

DEFAULT_THEME vs User Themes:

// DEFAULT_THEME (system)
{
  id: "DEFAULT_THEME",
  name: "Default",
  isSystem: true,
  canDelete: false,
  canRename: false
}

// User Theme
{
  id: "light",
  name: "Light Mode",
  isSystem: false,
  canDelete: true,
  canRename: true
}

Theme Data Storage

Themes are stored in the .creator project file format.

.creator File Structure:

{
  "version": "1.0",
  "project": {
    "name": "My Animation",
    "width": 1920,
    "height": 1080
  },
  "themes": {
    "DEFAULT_THEME": {
      "id": "DEFAULT_THEME",
      "name": "Default",
      "isSystem": true,
      "colors": {
        "primary-color": { "r": 33, "g": 150, "b": 243, "a": 1 },
        "bg-color": { "r": 255, "g": 255, "b": 255, "a": 1 }
      }
    },
    "light": {
      "id": "light",
      "name": "Light Mode",
      "isSystem": false,
      "colors": {
        "primary-color": { "r": 33, "g": 150, "b": 243, "a": 1 },
        "bg-color": { "r": 255, "g": 255, "b": 255, "a": 1 }
      }
    },
    "dark": {
      "id": "dark",
      "name": "Dark Mode",
      "isSystem": false,
      "colors": {
        "primary-color": { "r": 100, "g": 181, "b": 246, "a": 1 },
        "bg-color": { "r": 18, "g": 18, "b": 18, "a": 1 }
      }
    }
  },
  "activeThemeId": "light",
  "colorSlots": [
    {
      "assetId": "primary-color",
      "slotType": 2,
      "type": "SLOT"
    },
    {
      "assetId": "bg-color",
      "slotType": 2,
      "type": "SLOT"
    }
  ],
  "layers": [],
  "assets": []
}

Theme Serialization:

class ThemeSerializer {
  serialize(themes: Map<string, Theme>): any {
    const serialized = {};

    for (const [id, theme] of themes) {
      serialized[id] = {
        id: theme.id,
        name: theme.name,
        isSystem: theme.isSystem,
        colors: {},
      };

      // Serialize colors map
      for (const [slotId, color] of theme.colors) {
        serialized[id].colors[slotId] = {
          r: color.r,
          g: color.g,
          b: color.b,
          a: color.a,
        };
      }
    }

    return serialized;
  }

  deserialize(data: any): Map<string, Theme> {
    const themes = new Map();

    for (const [id, themeData] of Object.entries(data)) {
      const theme = new Theme(themeData.id, themeData.name, themeData.isSystem);

      // Deserialize colors
      for (const [slotId, colorData] of Object.entries(themeData.colors)) {
        theme.setColor(slotId, colorData);
      }

      themes.set(id, theme);
    }

    return themes;
  }
}

File Save Process:

async function saveProject() {
  // 1. Gather project data
  const projectData = {
    version: "1.0",
    project: getProjectMetadata(),
    themes: themeSerializer.serialize(themeManager.getAllThemes()),
    activeThemeId: themeManager.getActiveThemeId(),
    colorSlots: slotManager.serializeSlots(),
    layers: sceneGraph.serializeLayers(),
    assets: assetManager.serializeAssets(),
  };

  // 2. Convert to JSON
  const json = JSON.stringify(projectData, null, 2);

  // 3. Save to file
  await fs.writeFile(projectPath, json, "utf-8");

  // 4. Mark as saved
  projectState.markSaved();
}

File Load Process:

async function loadProject(filePath: string) {
  // 1. Read file
  const json = await fs.readFile(filePath, "utf-8");
  const data = JSON.parse(json);

  // 2. Load themes first
  const themes = themeSerializer.deserialize(data.themes);
  themeManager.setThemes(themes);

  // 3. Set active theme
  themeManager.setActiveTheme(data.activeThemeId);

  // 4. Load color slots (they reference themes)
  slotManager.deserializeSlots(data.colorSlots);

  // 5. Load layers and assets
  sceneGraph.deserializeLayers(data.layers);
  assetManager.deserializeAssets(data.assets);

  // 6. Render with active theme
  canvas.render();
}

Active Theme Management

How the editor manages the currently active theme.

Active Theme State:

class ThemeManager {
  private activeThemeId: string = "DEFAULT_THEME";
  private themes: Map<string, Theme> = new Map();
  private listeners: Set<ThemeChangeListener> = new Set();

  getActiveTheme(): Theme {
    return this.themes.get(this.activeThemeId);
  }

  setActiveTheme(themeId: string): void {
    // 1. Validate theme exists
    if (!this.themes.has(themeId)) {
      throw new Error(`Theme "${themeId}" not found`);
    }

    // 2. Store previous theme (for undo)
    const previousThemeId = this.activeThemeId;

    // 3. Update active theme
    this.activeThemeId = themeId;

    // 4. Update all slot values
    this.updateSlotsWithTheme(themeId);

    // 5. Trigger canvas re-render
    canvas.render();

    // 6. Notify listeners
    this.notifyThemeChange(previousThemeId, themeId);

    // 7. Add to undo stack
    undoManager.add({
      type: "THEME_CHANGE",
      previousTheme: previousThemeId,
      newTheme: themeId,
    });
  }

  private updateSlotsWithTheme(themeId: string): void {
    const theme = this.themes.get(themeId);

    // Update each slot with theme's color
    for (const slot of slotManager.getAllSlots()) {
      const color = theme.getColor(slot.id);
      slot.updateValue(color);
    }
  }

  private notifyThemeChange(oldThemeId: string, newThemeId: string): void {
    const event = {
      type: "theme_changed",
      oldTheme: oldThemeId,
      newTheme: newThemeId,
      timestamp: Date.now(),
    };

    for (const listener of this.listeners) {
      listener.onThemeChange(event);
    }
  }
}

Theme Change Propagation:

User Selects Theme "dark"
  ↓
ThemeManager.setActiveTheme("dark")
  ↓
Load "dark" theme colors
  ↓
Update all color slots with dark theme values
  │
  ├─ Slot "primary-color": #2196F3 → #64B5F6
  ├─ Slot "bg-color": #FFFFFF → #121212
  └─ Slot "text-color": #212121 → #FFFFFF
  ↓
Slots notify referenced properties
  │
  ├─ Button fill updates to #64B5F6
  ├─ Background fill updates to #121212
  └─ Text fill updates to #FFFFFF
  ↓
Canvas re-renders with new colors
  ↓
UI updates active theme indicator
  ↓
Undo operation added to history

Performance Optimization:

class ThemeManager {
  private renderScheduled = false;

  setActiveTheme(themeId: string): void {
    // ... theme change logic ...

    // Batch render updates
    this.scheduleRender();
  }

  updateSlotColor(themeId: string, slotId: string, color: Color): void {
    const theme = this.themes.get(themeId);
    theme.setColor(slotId, color);

    // Only update if this is the active theme
    if (themeId === this.activeThemeId) {
      const slot = slotManager.getSlot(slotId);
      slot.updateValue(color);

      // Batch render
      this.scheduleRender();
    }
  }

  private scheduleRender(): void {
    if (this.renderScheduled) return;

    this.renderScheduled = true;

    // Use requestAnimationFrame to batch multiple updates
    requestAnimationFrame(() => {
      canvas.render();
      this.renderScheduled = false;
    });
  }
}

Slot Distribution to Themes

When a new slot is created, it's automatically distributed to all themes.

Slot Distribution Process:

class SlotManager {
  createSlot(slotId: string, initialColor: Color): ColorSlot {
    // 1. Create slot
    const slot = new ColorSlot(slotId, initialColor);
    this.slots.set(slotId, slot);

    // 2. Add to DEFAULT_THEME
    const defaultTheme = themeManager.getTheme("DEFAULT_THEME");
    defaultTheme.setColor(slotId, initialColor);

    // 3. Distribute to all user themes
    const userThemes = themeManager.getUserThemes();
    for (const theme of userThemes) {
      // Use initial color as starting point
      theme.setColor(slotId, initialColor);

      // Mark as needing user review
      theme.markSlotAsNew(slotId);
    }

    // 4. Notify UI to prompt user for theme colors
    this.notifySlotCreated(slotId, userThemes.length);

    return slot;
  }

  private notifySlotCreated(slotId: string, themeCount: number): void {
    // Show notification to user
    notifications.show({
      type: "info",
      message: `Color slot "${slotId}" created. Define colors for ${themeCount} themes.`,
      action: {
        label: "Open Theme Manager",
        callback: () => panels.open("theme-manager", { focusSlot: slotId }),
      },
    });
  }
}

New Slot in Existing Themes:

// Before: Project with 2 themes
themes = {
  light: {
    colors: {
      primary: { r: 33, g: 150, b: 243, a: 1 },
      bg: { r: 255, g: 255, b: 255, a: 1 },
    },
  },
  dark: {
    colors: {
      primary: { r: 100, g: 181, b: 246, a: 1 },
      bg: { r: 18, g: 18, b: 18, a: 1 },
    },
  },
};

// User creates new slot "accent" with color #FF5722
slotManager.createSlot("accent", { r: 255, g: 87, b: 34, a: 1 });

// After: Slot added to all themes
themes = {
  light: {
    colors: {
      primary: { r: 33, g: 150, b: 243, a: 1 },
      bg: { r: 255, g: 255, b: 255, a: 1 },
      accent: { r: 255, g: 87, b: 34, a: 1 }, // New - needs review
    },
  },
  dark: {
    colors: {
      primary: { r: 100, g: 181, b: 246, a: 1 },
      bg: { r: 18, g: 18, b: 18, a: 1 },
      accent: { r: 255, g: 87, b: 34, a: 1 }, // New - needs adjustment for dark
    },
  },
};

// UI prompts user: "Review new color slot 'accent' in Dark theme"

Slot Validation:

class ThemeManager {
  validateThemeCompleteness(themeId: string): ValidationResult {
    const theme = this.themes.get(themeId);
    const allSlots = slotManager.getAllSlots();
    const errors: string[] = [];
    const warnings: string[] = [];

    for (const slot of allSlots) {
      if (!theme.hasColor(slot.id)) {
        errors.push(`Theme "${theme.name}" missing color for slot "${slot.id}"`);
      }
    }

    // Check for slots with default colors (may need customization)
    for (const [slotId, color] of theme.colors) {
      const defaultColor = this.getTheme("DEFAULT_THEME").getColor(slotId);
      if (colorsEqual(color, defaultColor)) {
        warnings.push(`Slot "${slotId}" in theme "${theme.name}" uses default color (consider customizing)`);
      }
    }

    return {
      valid: errors.length === 0,
      errors,
      warnings,
    };
  }
}

Undo/Redo Integration

Theme operations integrate with the undo/redo system.

Undoable Operations:

enum ThemeOperation {
  CREATE_THEME = "CREATE_THEME",
  DELETE_THEME = "DELETE_THEME",
  RENAME_THEME = "RENAME_THEME",
  CHANGE_ACTIVE_THEME = "CHANGE_ACTIVE_THEME",
  UPDATE_SLOT_COLOR = "UPDATE_SLOT_COLOR",
  DUPLICATE_THEME = "DUPLICATE_THEME",
}

interface ThemeUndoableAction {
  type: ThemeOperation;
  execute(): void;
  undo(): void;
  redo(): void;
}

Update Slot Color Undo:

class UpdateSlotColorAction implements ThemeUndoableAction {
  constructor(
    private themeId: string,
    private slotId: string,
    private oldColor: Color,
    private newColor: Color
  ) {}

  execute(): void {
    const theme = themeManager.getTheme(this.themeId);
    theme.setColor(this.slotId, this.newColor);

    // If this is active theme, update canvas
    if (themeManager.getActiveThemeId() === this.themeId) {
      const slot = slotManager.getSlot(this.slotId);
      slot.updateValue(this.newColor);
      canvas.render();
    }
  }

  undo(): void {
    const theme = themeManager.getTheme(this.themeId);
    theme.setColor(this.slotId, this.oldColor);

    if (themeManager.getActiveThemeId() === this.themeId) {
      const slot = slotManager.getSlot(this.slotId);
      slot.updateValue(this.oldColor);
      canvas.render();
    }
  }

  redo(): void {
    this.execute();
  }
}

Change Active Theme Undo:

class ChangeActiveThemeAction implements ThemeUndoableAction {
  constructor(
    private previousThemeId: string,
    private newThemeId: string
  ) {}

  execute(): void {
    themeManager.setActiveTheme(this.newThemeId);
  }

  undo(): void {
    themeManager.setActiveTheme(this.previousThemeId);
  }

  redo(): void {
    this.execute();
  }
}

Create Theme Undo:

class CreateThemeAction implements ThemeUndoableAction {
  private createdTheme: Theme;

  constructor(
    private themeName: string,
    private baseThemeId?: string
  ) {}

  execute(): void {
    // Create theme
    this.createdTheme = themeManager.createTheme(this.themeName, this.baseThemeId);
  }

  undo(): void {
    // Delete the created theme
    themeManager.deleteTheme(this.createdTheme.id);
  }

  redo(): void {
    // Re-add the theme
    themeManager.addTheme(this.createdTheme);
  }
}

Undo Manager Integration:

class ThemeManager {
  updateSlotColor(themeId: string, slotId: string, newColor: Color): void {
    // Get current color for undo
    const theme = this.themes.get(themeId);
    const oldColor = theme.getColor(slotId);

    // Create undoable action
    const action = new UpdateSlotColorAction(themeId, slotId, oldColor, newColor);

    // Execute and add to undo stack
    action.execute();
    undoManager.add(action);
  }

  setActiveTheme(themeId: string): void {
    const previousThemeId = this.activeThemeId;

    // Create undoable action
    const action = new ChangeActiveThemeAction(previousThemeId, themeId);

    // Execute and add to undo stack
    action.execute();
    undoManager.add(action);
  }
}

Theme Events and Lifecycle

Theme operations trigger events that other systems can listen to.

Theme Event Types:

interface ThemeEvent {
  type: ThemeEventType;
  timestamp: number;
  data: any;
}

enum ThemeEventType {
  THEME_CREATED = "theme_created",
  THEME_DELETED = "theme_deleted",
  THEME_RENAMED = "theme_renamed",
  THEME_ACTIVATED = "theme_activated",
  SLOT_COLOR_CHANGED = "slot_color_changed",
  THEME_VALIDATED = "theme_validated",
}

Event Emitter:

class ThemeManager extends EventEmitter {
  createTheme(name: string, baseThemeId?: string): Theme {
    // Create theme
    const theme = new Theme(name, generateId());

    // Copy colors from base theme if specified
    if (baseThemeId) {
      const baseTheme = this.themes.get(baseThemeId);
      for (const [slotId, color] of baseTheme.colors) {
        theme.setColor(slotId, color);
      }
    }

    // Add to themes map
    this.themes.set(theme.id, theme);

    // Emit event
    this.emit("theme_created", {
      type: ThemeEventType.THEME_CREATED,
      timestamp: Date.now(),
      data: {
        themeId: theme.id,
        themeName: theme.name,
        basedOn: baseThemeId,
      },
    });

    return theme;
  }

  setActiveTheme(themeId: string): void {
    const previousThemeId = this.activeThemeId;
    this.activeThemeId = themeId;

    // ... update slots and render ...

    // Emit event
    this.emit("theme_activated", {
      type: ThemeEventType.THEME_ACTIVATED,
      timestamp: Date.now(),
      data: {
        previousTheme: previousThemeId,
        activeTheme: themeId,
      },
    });
  }

  updateSlotColor(themeId: string, slotId: string, color: Color): void {
    const theme = this.themes.get(themeId);
    const oldColor = theme.getColor(slotId);
    theme.setColor(slotId, color);

    // Emit event
    this.emit("slot_color_changed", {
      type: ThemeEventType.SLOT_COLOR_CHANGED,
      timestamp: Date.now(),
      data: {
        themeId,
        slotId,
        oldColor,
        newColor: color,
      },
    });

    // Update canvas if active theme
    if (themeId === this.activeThemeId) {
      const slot = slotManager.getSlot(slotId);
      slot.updateValue(color);
      canvas.render();
    }
  }
}

Event Listeners:

// UI updates
themeManager.on("theme_created", (event) => {
  themeListUI.addTheme(event.data.themeId);
  notifications.show(`Theme "${event.data.themeName}" created`);
});

themeManager.on("theme_activated", (event) => {
  themeListUI.setActive(event.data.activeTheme);
  canvas.render();
});

// Analytics tracking
themeManager.on("slot_color_changed", (event) => {
  analytics.track("theme_color_updated", {
    theme: event.data.themeId,
    slot: event.data.slotId,
  });
});

// Auto-save on theme changes
themeManager.on("theme_created", () => projectManager.markDirty());
themeManager.on("theme_deleted", () => projectManager.markDirty());
themeManager.on("slot_color_changed", () => projectManager.markDirty());

Theme Import/Export Sync

When importing animations, themes are merged with conflict resolution.

Theme Import Process:

class ThemeImporter {
  async importAnimation(lottieJSON: any): Promise<void> {
    // 1. Parse imported animation's themes
    const importedThemes = this.parseThemes(lottieJSON);

    // 2. Detect conflicts
    const conflicts = this.detectConflicts(importedThemes);

    // 3. Resolve conflicts
    if (conflicts.length > 0) {
      const resolution = await this.promptUserForResolution(conflicts);
      this.applyResolution(importedThemes, resolution);
    }

    // 4. Merge themes
    for (const theme of importedThemes) {
      themeManager.addTheme(theme);
    }

    // 5. Merge slots
    const importedSlots = this.parseSlots(lottieJSON);
    for (const slot of importedSlots) {
      slotManager.mergeSlot(slot);
    }
  }

  private detectConflicts(importedThemes: Theme[]): Conflict[] {
    const conflicts: Conflict[] = [];
    const existingThemes = themeManager.getAllThemes();

    for (const importedTheme of importedThemes) {
      const existing = existingThemes.get(importedTheme.id);

      if (existing) {
        conflicts.push({
          type: "theme_id_conflict",
          existingTheme: existing,
          importedTheme: importedTheme,
        });
      }
    }

    return conflicts;
  }

  private async promptUserForResolution(conflicts: Conflict[]): Promise<ResolutionStrategy> {
    // Show dialog to user
    return await showConflictDialog({
      conflicts,
      options: [
        { value: "rename", label: "Rename imported themes" },
        { value: "merge", label: "Merge with existing themes" },
        { value: "skip", label: "Skip conflicting themes" },
        { value: "replace", label: "Replace existing themes" },
      ],
    });
  }
}

Performance Characteristics

Theme operations are optimized for smooth user experience.

Operation Performance:

// Typical performance metrics
const performanceMetrics = {
  createTheme: "< 1ms", // Nearly instant
  deleteTheme: "< 1ms", // Nearly instant
  renameTheme: "< 1ms", // Nearly instant
  updateSlotColor: "< 5ms", // Color update + slot propagation
  switchActiveTheme: "10-50ms", // Depends on slot count and complexity
  validateTheme: "< 5ms", // Check all slots defined
  exportThemes: "50-200ms", // Depends on theme count and file size
  importThemes: "100-500ms", // Depends on file size and conflicts
};

Optimization Techniques:

// 1. Batched Updates
class ThemeManager {
  private updateBatch: Map<string, Color> = new Map();
  private batchScheduled = false;

  updateSlotColor(themeId: string, slotId: string, color: Color): void {
    // Add to batch
    this.updateBatch.set(`${themeId}:${slotId}`, color);

    // Schedule batch processing
    if (!this.batchScheduled) {
      this.batchScheduled = true;
      requestAnimationFrame(() => this.processBatch());
    }
  }

  private processBatch(): void {
    // Process all updates at once
    for (const [key, color] of this.updateBatch) {
      const [themeId, slotId] = key.split(":");
      this.applyColorUpdate(themeId, slotId, color);
    }

    // Clear batch
    this.updateBatch.clear();
    this.batchScheduled = false;

    // Single render
    canvas.render();
  }
}

// 2. Lazy Validation
class ThemeManager {
  private validationCache: Map<string, ValidationResult> = new Map();

  validateTheme(themeId: string): ValidationResult {
    // Check cache
    if (this.validationCache.has(themeId)) {
      return this.validationCache.get(themeId);
    }

    // Perform validation
    const result = this.performValidation(themeId);

    // Cache result
    this.validationCache.set(themeId, result);

    return result;
  }

  invalidateValidationCache(themeId?: string): void {
    if (themeId) {
      this.validationCache.delete(themeId);
    } else {
      this.validationCache.clear();
    }
  }
}

Best Practices

Tips for effective theme management.

Define All Slots

Complete Themes:

  • Every theme should define all color slots
  • Undefined slots cause inconsistency
  • Theme Manager warns about undefined slots
  • Define all before exporting

Verification:

  • Review each theme
  • Check for undefined slot warnings
  • Test each theme thoroughly
  • Ensure no missing colors

Maintain Contrast

Accessibility:

  • Text must meet WCAG contrast ratios (4.5:1 minimum for body text)
  • Test contrast in both themes
  • Use contrast checker tools
  • Adjust colors for readability

Contrast Tips:

  • Light theme: Dark text on light background
  • Dark theme: Light text on dark background
  • Sufficient contrast for all text
  • Test with actual users if possible

Test Both Themes

Quality Assurance:

  • Preview animation in each theme
  • Verify all elements visible
  • Check readability
  • Test on actual devices
  • Ensure smooth theme switching

Testing Checklist:

  • ✅ All colors defined
  • ✅ Text readable in all themes
  • ✅ Proper contrast ratios
  • ✅ Visual hierarchy maintained
  • ✅ Animation looks good in each theme
  • ✅ No broken elements
  • ✅ Theme switching smooth

Keep Themes Consistent

Consistency Rules:

  • Non-brand colors should be similar across themes
  • Maintain visual hierarchy
  • Similar color relationships
  • Predictable user experience

Example:

  • If Primary is more saturated than Secondary in Light theme
  • Primary should be more saturated in Dark theme too
  • Maintain color relationships

Document Themes

Theme Documentation:

  • Document purpose of each theme
  • Note color values and rationale
  • Guide for future updates
  • Handoff to developers/designers

Documentation Example:

Light Theme:
- Purpose: Daytime use, standard mode
- Background: #FFFFFF (pure white)
- Primary: #2196F3 (brand blue)
- Text: #212121 (near-black, WCAG AAA)

Dark Theme:
- Purpose: Low-light use, dark mode
- Background: #121212 (Material dark gray, not pure black)
- Primary: #64B5F6 (lighter blue, adjusted for dark bg)
- Text: #FFFFFF (white, WCAG AAA on background)

Keyboard Shortcuts

Theme Management:

  • No standard theme-specific shortcuts
  • Use general navigation shortcuts

General:

  • Cmd/Ctrl + Z - Undo theme changes
  • Cmd/Ctrl + Y - Redo
  • Enter - Confirm theme name edit
  • Escape - Cancel edit or close panel

See Complete Keyboard Shortcuts for all shortcuts.

Common Issues

"Can't find Theme Manager"

  • Check left sidebar for "Themes" or "Theme Manager"
  • Look in Window or View menu
  • Feature may require specific Lottie Creator version
  • Check panel tabs/switcher

"Theme not updating canvas"

  • Verify theme is applied (active)
  • Check properties are linked to slots
  • Ensure slots defined in theme
  • Refresh editor if needed
  • Try switching to another theme and back

"Undefined slots warning"

  • Some slots not defined in current theme
  • Go through slot list
  • Assign color to each undefined slot
  • Warning should disappear
  • All themes should define all slots

"Theme exported but not working"

  • Verify using dotLottie format (themes require dotLottie)
  • Check player supports themes (Lottie-web 5.10+)
  • Ensure themes properly exported
  • Verify theme selection in player code
  • Test with modern player

"Can't delete theme"

  • Must have at least one theme
  • Cannot delete active theme (apply different theme first)
  • Check if theme is default (change default first)
  • Verify permissions

"Colors not changing when editing theme"

  • Ensure correct theme selected for editing
  • Verify editing active theme (changes show immediately)
  • If editing inactive theme, apply it to see changes
  • Check slot properly linked to properties

Tips for Theme Manager Success

Start with Two Themes

Begin Simple:

  • Start with Light and Dark themes
  • Most common use case
  • Easy to manage
  • Add more only if needed
  • Test thoroughly before adding complexity

Duplicate to Save Time

Use Duplication:

  • Create first theme completely
  • Duplicate for variations
  • Change only necessary colors
  • Faster than starting from scratch
  • Maintains consistency

Preview Frequently

Regular Preview:

  • Apply theme to see changes
  • Switch between themes often
  • Catch issues early
  • Verify quality continuously
  • Adjust as needed

Name Descriptively

Clear Names:

  • "Light Mode", "Dark Mode" > "Theme 1", "Theme 2"

  • "Brand A - Red", "Brand B - Blue" > "Theme A", "Theme B"

  • Self-documenting

  • Easier to manage

  • Professional approach

Define Complete Themes

No Undefined Slots:

  • Define all slots in all themes
  • Avoid undefined slot warnings
  • Ensures consistency
  • Professional quality
  • Export-ready

Test on Target Platform

Platform Testing:

  • Export and test in actual player
  • Verify theme switching works
  • Check performance
  • Test on target devices
  • Ensure compatibility
Last updated: April 10, 2026 at 9:12 AMEdit this page