Theme and Component Integration Guide

Back to Documentation Hub

Theme and Component Integration Guide

Overview

This guide explains how to properly integrate Bravura SDK themes with premium components, covering theme architecture, automatic widget styling, premium button integration, and command preservation across theme changes.

What You'll Learn:

---

Table of Contents

  1. Available Themes
  2. Theme System Architecture
  3. Basic Theme Usage
  4. Premium Button Integration
  5. Theme Integration Patterns
  6. Advanced Features
  7. Best Practices
  8. Troubleshooting

---

Available Themes

Standard Themes (All Tiers)

Theme Description Best For
default Standard Light Daytime use, professional look
dark Standard Dark Reduced eye strain
black Standard Black OLED displays, night mode
white Standard White Maximum clarity
blue_deep Standard Blue Corporate applications
pink_professional Standard Pink Modern, elegant design
ocean Standard Ocean Calming, professional

Premium Themes (Professional+ / Enterprise)

Theme Tier Features
wigley_site Professional+ Custom teal/cyan, animated background, premium buttons
platinum Enterprise Metallic silver, shimmer effects, luxury appearance
custom Enterprise Fully customizable brand colors, white-label ready

---

Theme System Architecture

Theme System Architecture


ProfessionalThemeManager
├── Standard Themes (all tiers)
│   ├── default, dark, black, white
│   ├── blue_deep, pink_professional, ocean
│   └── Provide: base colors, button styles, widget styling
│
└── Premium Themes (Enterprise tier)
    ├── wigley_site, platinum, custom
    └── Provide: everything above + ambient backgrounds + glass panels

For custom branding: See Custom Branding Theme Guide for complete documentation on customizing the custom theme with your company's brand colors.

High-Contrast Panel Colors (All Themes)

All Bravura themes now include specialized high-contrast panel background colors for improved visual hierarchy:

Token Purpose Default (Light) Usage Example
`panel_input_bg` Input/settings panels `#E8E8E8` Form sections, configuration areas
`panel_progress_bg` Progress tracking panels `#F5F5F5` Status updates, progress bars
`panel_results_bg` Results display panels `#ECECEC` Output data, analytics displays

Benefits:

Example Usage:


# Get theme colors dynamically
theme_colors = self.theme_manager.get_current_theme_colors() if self.theme_manager else {}

# Apply to input panel
input_frame = tk.Frame(
    parent,
    bg=theme_colors.get('panel_input_bg', '#E8E8E8'),
    relief="flat",
    bd=1
)

# Apply to progress panel
progress_frame = tk.Frame(
    parent,
    bg=theme_colors.get('panel_progress_bg', '#F5F5F5'),
    relief="flat",
    bd=1
)

# Apply to results panel
results_frame = tk.Frame(
    parent,
    bg=theme_colors.get('panel_results_bg', '#ECECEC'),
    relief="flat",
    bd=1
)

Component Compatibility

Component Works with Standard Themes Works with Premium Themes
`PremiumButton` ✅ Yes (with tokens) ✅ Yes
`CompactPremiumButton` ✅ Yes (with tokens) ✅ Yes
`Toast` (Standard) ✅ Yes (theme-aware) ✅ Yes (theme-aware)
`Toast` (Premium) ⚠️ Pro/Enterprise only ✅ Yes (glass panel)
`AmbientBackground` ⚠️ Manual only ✅ Auto-created
`GlassPanel` ⚠️ Manual only ✅ Auto-created
`ThemeManager` ✅ Yes ✅ Yes

---

Premium Button Integration

How Buttons Resolve Colors

Premium buttons use a 3-tier color resolution system:

  1. Direct Hex Colors - Fixed colors like #22C55E for success buttons
  2. Theme Tokens - Dynamic colors from the active theme (e.g., hover_bg, accent_color_1)
  3. Intelligent Fallback - Calculated colors when tokens are missing

# Button color resolution order:
# 1. Check if color is already hex (e.g., "#22C55E") → use directly
# 2. Check theme tokens for key (e.g., "hover_bg") → use if found
# 3. Calculate from related colors (e.g., darken button_bg for hover)
# 4. Use neutral gray fallback (NOT teal, as of v2.0.1)

Button Styles and Their Token Requirements

Primary Button


PremiumButton(parent, text="Save", style="primary")
# Requires tokens:
# - accent_color_1  → background color
# - button_fg       → text color
# - hover_bg        → hover background
# - active_bg       → click background

Secondary Button


PremiumButton(parent, text="Cancel", style="secondary")
# Requires tokens:
# - background_secondary  → background color
# - accent_color_1        → text color
# - hover_bg              → hover background
# - active_bg             → click background

Fixed Style Buttons (Success, Danger, Warning)


PremiumButton(parent, text="Confirm", style="success")
# Uses FIXED hex colors - no theme tokens needed
# Success: #22C55E, Danger: #EF4444, Warning: #F59E0B

---

Toast Notification Integration

How Toast Notifications Use Themes

Toast notifications automatically adapt to the active theme by pulling colors from theme tokens:


# Toast notification color resolution:
# 1. Background → theme's "background_secondary" token
# 2. Text → theme's "text_main" and "text_secondary" tokens
# 3. Border → theme's "border_color" token
# 4. Info icon → theme's "accent_color_1" token
# 5. Success icon → theme's "success_color" token
# 6. Warning icon → theme's "warning_color" token
# 7. Error icon → theme's "error_color" token

Standard vs Premium Toast Styles

Standard Toast (All Tiers)


from bravura.components import create_toast_center

# Create toast center with theme colors
theme_colors = theme_manager.get_current_theme_colors()
toast_center = create_toast_center(root, theme_colors)

# Show notification - automatically uses theme colors
toast_center.show("Operation complete", kind="success")
# Result: Background from theme, success icon color from theme

Premium Toast (Professional/Enterprise)


# Create with premium style
toast_center = create_toast_center(
    root,
    theme_colors,
    default_style="premium"  # Glass panel effect
)

# Show premium toast
toast_center.show("Premium notification", kind="info", style="premium")
# Result: Frosted glass panel with theme-colored accents

Theme Change Handling

Toast notifications created after a theme change automatically use the new theme colors:


def switch_theme(self, new_theme):
    # Apply new theme
    self.theme_manager.apply_theme(new_theme)

    # Recreate toast center with new theme colors
    self.toast_center = create_toast_center(
        self.root,
        self.theme_manager.get_current_theme_colors()
    )

    # New toasts now use new theme
    self.toast_center.show("Theme changed!", kind="success")

Important: Existing visible toasts retain their original theme colors. Only new toasts use the updated theme.

---

Basic Theme Usage

Initializing the Theme Manager


from bravura.themes.theme_manager import create_theme_manager

class MyApp:
    def __init__(self):
        self.root = tk.Tk()

        # Initialize theme manager FIRST
        self.theme_manager = create_theme_manager(self)

        # Create UI
        self.create_ui()

        # Apply theme
        self.theme_manager.apply_theme("wigley_site")

Switching Themes


def switch_theme(self, theme_name):
    """Switch to a different theme."""
    self.theme_manager.apply_theme(theme_name)
    # Theme preference is automatically saved

Getting Current Theme Info


# Get current theme name
current = self.theme_manager.current_theme_name

# Get theme color palette
colors = self.theme_manager.get_current_theme_colors()

# Get available themes (license-aware)
available = self.theme_manager.available_themes

---

Theme Integration Patterns

Pattern 1: Standard Application (Recommended)

For most applications, use the automatic theme system:


import tkinter as tk
from bravura import ProfessionalThemeManager, PremiumButton

class MyApp:
    def __init__(self):
        self.root = tk.Tk()

        # Step 1: Initialize theme manager FIRST
        self.theme_manager = ProfessionalThemeManager(
            self,
            config_file="gui_config.json"
        )

        # Step 2: CRITICAL - Store reference on root for button token lookup
        # Without this line, buttons will use fallback colors (gray/blue)
        self.root.theme_manager = self.theme_manager

        # Step 3: Create GUI after theme manager
        self.create_gui()

    def create_gui(self):
        # Buttons automatically get tokens from theme_manager
        save_btn = PremiumButton(
            self.root,
            text="Save",
            style="primary"
        )
        save_btn.pack()

Key Points:

Pattern 2: Manual Token Passing (Legacy)

If you need explicit control over tokens:


# Get current theme colors
theme_colors = self.theme_manager.get_current_theme_colors()

# Pass to button manually
btn = PremiumButton(
    parent,
    text="Action",
    style="primary",
    theme_tokens=theme_colors
)

Warning: Manual tokens don't update when themes change. Use Pattern 1 for dynamic theming.

---

Theme Changes and Component Updates

How Theme Changes Work

When you call theme_manager.apply_theme(theme_name):

  1. Theme Manager Updates

- Loads new color palette from PROFESSIONAL_THEMES

- Updates TTK styles (Button.TButton, TNotebook.Tab, TCombobox, etc.)

- Configures widget colors (Entry, Text, Listbox, Treeview)

  1. Ambient Background Recreation

- Premium themes: Destroys old ambient background, creates new one with theme colors

- Standard themes: No ambient background (premium feature)

  1. Button Color Persistence

- Buttons do NOT automatically refresh colors on theme change

- Buttons created AFTER theme change get new colors

- Existing buttons keep their initialization colors

  1. Custom Widget Updates

- tk.Frame, tk.Label, and other custom widgets need manual refresh

- Recommended pattern: store widget references and update in refresh_theme_colors() method

- High-contrast panel colors must be manually applied on theme change

Best Practice: Button Creation Timing


# CORRECT: Create buttons after applying theme
self.theme_manager.apply_theme("blue_deep")
self.create_buttons()  # Buttons get blue_deep colors

# INCORRECT: Create buttons before theme change
self.create_buttons()  # Buttons get default colors
self.theme_manager.apply_theme("blue_deep")  # Buttons DON'T update

Dynamic Theme Switching

For applications that allow runtime theme switching:


def switch_theme(self, new_theme):
    # Apply the new theme
    self.theme_manager.apply_theme(new_theme)

    # Option 1: Recreate UI (simple but disruptive)
    self.destroy_gui()
    self.create_gui()

    # Option 2: Update individual buttons (complex but smooth)
    for button in self.all_buttons:
        if hasattr(button, '_refresh_colors_from_current_theme'):
            button._refresh_colors_from_current_theme()

    # Option 3: Update stored widgets with refresh method (RECOMMENDED)
    self.refresh_theme_colors()

Theme Refresh Pattern (Recommended)

The Admin Dashboard Pattern provides the most robust theme switching:


class MyApp:
    def __init__(self):
        self.root = tk.Tk()
        self.theme_manager = ProfessionalThemeManager(self)

        # Dictionary to store all themed widgets for updates
        self.themed_widgets = {}

        self.create_ui()

    def create_ui(self):
        """Create UI and store widget references."""
        theme_colors = self.theme_manager.get_current_theme_colors()

        # Create panel with high-contrast color
        input_panel = tk.Frame(
            self.root,
            bg=theme_colors.get('panel_input_bg', '#E8E8E8'),
            relief="flat",
            bd=1
        )
        input_panel.grid(row=0, column=0, sticky="nsew")

        # Store reference for theme updates
        self.themed_widgets['input_panel'] = input_panel

        # Create button and store reference
        action_btn = tk.Button(
            input_panel,
            text="Process",
            bg=theme_colors.get('accent_color_1', '#0078d7'),
            fg="white",
            cursor="hand2"
        )
        action_btn.pack(pady=10)
        self._add_button_hover(action_btn)
        self.themed_widgets['action_btn'] = action_btn

    def apply_theme(self, theme_id: str):
        """Apply theme and refresh all widgets."""
        if self.theme_manager:
            self.theme_manager.apply_theme(theme_id)
            self.refresh_theme_colors()

    def refresh_theme_colors(self):
        """Refresh all widget colors after theme change."""
        if not self.theme_manager:
            return

        # Get new theme colors dynamically
        theme_colors = self.theme_manager.get_current_theme_colors()

        # Update all stored widgets
        for widget_name, widget in self.themed_widgets.items():
            if not widget or not widget.winfo_exists():
                continue

            try:
                # Update frames/containers
                if 'panel' in widget_name or 'frame' in widget_name:
                    if 'input' in widget_name:
                        widget.configure(bg=theme_colors.get('panel_input_bg', '#E8E8E8'))
                    elif 'progress' in widget_name:
                        widget.configure(bg=theme_colors.get('panel_progress_bg', '#F5F5F5'))
                    elif 'results' in widget_name:
                        widget.configure(bg=theme_colors.get('panel_results_bg', '#ECECEC'))

                # Update buttons - recalculate hover colors
                elif 'btn' in widget_name or 'button' in widget_name:
                    new_bg = theme_colors.get('accent_color_1', '#0078d7')
                    widget.configure(bg=new_bg)
                    # Recalculate hover colors dynamically
                    widget._original_bg = new_bg
                    widget._hover_bg = self._calculate_hover_color(new_bg)

                # Update labels
                elif 'label' in widget_name:
                    parent_bg = theme_colors.get('panel_input_bg', '#E8E8E8')
                    widget.configure(
                        bg=parent_bg,
                        fg=theme_colors.get('text_main', '#000000')
                    )

            except Exception as e:
                print(f"Warning: Could not update {widget_name}: {e}")

        # Update root window
        if self.root:
            self.root.configure(bg=theme_colors.get('background_primary', '#F0F0F0'))

    def _calculate_hover_color(self, base_color):
        """Calculate a lighter hover color from base color."""
        try:
            # Remove # if present
            hex_color = base_color.lstrip('#')
            # Convert to RGB
            r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
            # Make it lighter (increase each channel by 15%)
            r = min(255, int(r * 1.15))
            g = min(255, int(g * 1.15))
            b = min(255, int(b * 1.15))
            return f'#{r:02x}{g:02x}{b:02x}'
        except:
            return base_color

    def _add_button_hover(self, button):
        """Add theme-aware hover effect to tk.Button widgets."""
        original_color = button.cget("bg")
        button._original_bg = original_color
        button._hover_bg = self._calculate_hover_color(original_color)

        def on_enter(e):
            if button["state"] != "disabled":
                button["bg"] = button._hover_bg

        def on_leave(e):
            if button["state"] != "disabled":
                button["bg"] = button._original_bg

        button.bind("<Enter>", on_enter)
        button.bind("<Leave>", on_leave)

Key Benefits of This Pattern:

  1. Centralized Widget Storage: All themed widgets stored in one dictionary
  2. Dynamic Color Retrieval: Gets fresh colors from theme manager on each refresh
  3. Systematic Updates: Iterates through all widgets and updates based on naming convention
  4. Theme-Aware Hover Effects: Dynamically recalculates button hover colors on theme change
  5. Robust Error Handling: Continues updating other widgets if one fails
  6. High-Contrast Panel Support: Properly applies new panel colors on theme switch

When to Use:

---

Ambient Background Integration

Automatic Creation (Premium Themes Only)

Premium themes automatically create ambient backgrounds:


# Apply premium theme
self.theme_manager.apply_theme("wigley_site")
# → Ambient background created automatically
# → Available as self.theme_manager.wigley_ambient

self.theme_manager.apply_theme("platinum")
# → Old ambient destroyed, new one created
# → Available as self.theme_manager.platinum_ambient

Manual Creation (Any Theme)

To add ambient background to standard themes:


from bravura.components import AmbientBackground

# Get current theme colors
theme_colors = self.theme_manager.get_current_theme_colors()

# Create ambient background with theme colors
self.ambient_bg = AmbientBackground(
    parent_widget=self.root,
    width=800,
    height=600,
    theme_tokens=theme_colors,  # Pass theme colors here!
    enable_animation=True,
    animation_speed="medium",
    particle_count=20,
    glow_effects=True
)

# Store reference for cleanup
self.ambient_backgrounds.append(self.ambient_bg)

Important: Ambient backgrounds do NOT auto-update on theme changes. You must destroy and recreate them.

Ambient Background Lifecycle


# Create
ambient = AmbientBackground(
    parent_widget=parent,
    theme_tokens=theme_manager.get_current_theme_colors()
)

# When theme changes, destroy old and create new
old_ambient.destroy()
new_ambient = AmbientBackground(
    parent_widget=parent,
    theme_tokens=theme_manager.get_current_theme_colors()  # New theme colors!
)

# Clean up reference
del old_ambient

Complete Theme Change Example


def switch_theme(self, new_theme):
    # Apply the new theme
    self.theme_manager.apply_theme(new_theme)

    # Destroy old ambient background (if exists)
    if hasattr(self, 'ambient_bg') and self.ambient_bg:
        try:
            self.ambient_bg.destroy()
        except Exception as e:
            print(f"Warning: Could not destroy ambient background: {e}")

    # Create new ambient background with new theme colors
    self.ambient_bg = AmbientBackground(
        parent_widget=self.root,
        width=self.root.winfo_width(),
        height=self.root.winfo_height(),
        theme_tokens=self.theme_manager.get_current_theme_colors(),  # New theme!
        enable_animation=True,
        animation_speed="medium",
        particle_count=20,
        glow_effects=True
    )

---

Common Integration Mistakes

Mistake 1: Creating Buttons Before Theme Manager


# WRONG
self.create_buttons()  # Buttons have no theme context
self.theme_manager = ProfessionalThemeManager(self)  # Too late!

# RIGHT
self.theme_manager = ProfessionalThemeManager(self)  # Theme first!
self.root.theme_manager = self.theme_manager  # Store reference
self.create_buttons()  # Buttons can find theme_manager

Mistake 2: Expecting Buttons to Auto-Update on Theme Change


# WRONG EXPECTATION
btn = PremiumButton(parent, text="Save", style="primary")
self.theme_manager.apply_theme("dark")  # Button colors DON'T change

# CORRECT APPROACH
self.theme_manager.apply_theme("dark")
btn = PremiumButton(parent, text="Save", style="primary")  # New button with dark colors

Mistake 3: Using Ambient Background in Standard Themes Without Manual Creation


# WRONG - Standard themes don't auto-create ambient backgrounds
self.theme_manager.apply_theme("dark")
# No ambient background created

# RIGHT - Manually create if needed
self.theme_manager.apply_theme("dark")
theme_colors = self.theme_manager.get_current_theme_colors()
self.ambient_bg = create_ambient_background(self.root, theme_colors)

Mistake 4: Not Storing theme_manager Reference on Root ⚠️ MOST COMMON ERROR

Symptom: Buttons appear gray/blue instead of theme colors, turn gray on hover


# WRONG - Buttons can't find theme_manager
self.theme_manager = ProfessionalThemeManager(self)
btn = PremiumButton(self.root, text="Save")
# Result: Gray button (#444444) instead of theme color
# Hover: Turns gray because fallback system activates

# RIGHT - Store reference for button lookup
self.theme_manager = ProfessionalThemeManager(self)
self.root.theme_manager = self.theme_manager  # CRITICAL LINE!
btn = PremiumButton(self.root, text="Save")
# Result: Proper theme colors (teal, blue, etc.)
# Hover: Proper theme hover colors

Why This Happens:

---

Button Hover Colors (v2.0.1 Fix)

Issue Background

Prior to v2.0.1, buttons would permanently change to teal on hover in standard themes. This was caused by:

  1. Secondary buttons using background_primary as hover color
  2. Fallback system defaulting to teal (#20C6B7) when colors couldn't be resolved

Current Behavior (v2.0.1+)

Migration Guide

If you have custom themes or manual button styling:


# OLD (pre-v2.0.1) - could cause teal hover issue
btn = PremiumButton(parent, text="Cancel", style="secondary")
# If background_primary was missing → fell back to teal

# NEW (v2.0.1+) - works reliably
btn = PremiumButton(parent, text="Cancel", style="secondary")
# If hover_bg is missing → calculates from button_bg
# Final fallback is neutral gray, not teal

---

Testing Theme Integration

Checklist for Theme Testing

Debug: Button Color Resolution


# Add this to debug button colors
btn = PremiumButton(parent, text="Test", style="primary")
print(f"Button bg: {btn.bg_normal}")
print(f"Hover bg: {btn.bg_hover}")
print(f"Active bg: {btn.bg_active}")
print(f"Tokens: {btn.tokens}")

Expected output with working theme:


Button bg: #0078d7  (or theme-specific color)
Hover bg: #0091ff   (slightly lighter/darker than button_bg)
Active bg: #005fa3  (even more contrast)
Tokens: {'accent_color_1': '#0078d7', 'hover_bg': '#0091ff', ...}

---

Advanced: Custom Theme Creation

Defining a Custom Theme


# Add to PROFESSIONAL_THEMES in theme_manager.py
PROFESSIONAL_THEMES["my_custom_theme"] = {
    "name": "My Custom Theme",
    "description": "Custom branding for my app",
    "tier": "enterprise",  # or "standard"

    # REQUIRED tokens for buttons:
    "button_bg": "#4F46E5",
    "button_fg": "#FFFFFF",
    "hover_bg": "#6366F1",
    "active_bg": "#3730A3",

    # REQUIRED tokens for general UI:
    "text_main": "#FFFFFF",
    "text_secondary": "#E2E8F0",
    "background_primary": "#1E293B",
    "background_secondary": "#334155",
    "accent_color_1": "#4F46E5",
    "accent_color_2": "#06B6D4",

    # Optional tokens:
    "slider_trough": "#334155",
    "log_bg": "#0F172A",
    "log_fg": "#06B6D4",
    "border_color": "#4F46E5",
    "success_color": "#059669",
    "warning_color": "#D97706",
    "error_color": "#DC2626",
}

Custom Theme with Ambient Background

For enterprise-tier custom themes, add ambient background support:


# In ProfessionalThemeManager.apply_theme()
elif theme_id == "my_custom_theme":
    colors = PROFESSIONAL_THEMES["my_custom_theme"]
    self._apply_base_widget_styles(colors)

    # Create custom ambient background
    custom_tokens = {
        "bg_ink": colors["background_primary"],
        "teal": colors["accent_color_1"],
        "glow_teal": f"rgba{self._hex_to_rgba(colors['accent_color_1'], 0.08)}",
    }

    self.custom_ambient = create_ambient_background(
        self.app.root, custom_tokens, enable_animation=True
    )

---

Summary

Key Takeaways

  1. Initialize theme_manager FIRST, before creating any widgets
  2. Store theme_manager reference on root widget for button token lookup
  3. Premium themes auto-create ambient backgrounds, standard themes don't
  4. Buttons don't auto-update on theme change - create them after applying theme
  5. Secondary buttons now use hover_bg token (fixed in v2.0.1)
  6. Fallback colors are neutral gray, not teal (fixed in v2.0.1)

When to Use Each Pattern

---

Version History

---

See Also

Last Updated: 2025-10-07

Version: 2.0

Applies to: Bravura v2.0.0+