Bravura Button Component Usage Guide

Back to Documentation Hub

Bravura Button Component Usage Guide

Choosing the Right Button for Your Application

Complete guide to premium buttons, theme integration, and command preservation

---

Quick Reference

Component Use Case Size Features
PremiumButton Hero actions Large Loading, icons, bold
CompactPremiumButton Toolbars, dense UIs Compact Premium colors, compact
ttk.Button (themed) Forms, tables Standard Theme colors, standard
ButtonFactory Auto-selection Varies Intelligent choice

---

Decision Tree


┌─ Is this the PRIMARY action on the page? (e.g., Save, Submit, Publish)
│
├─ YES ──► Use PremiumButton
│           • Shows loading states during operations
│           • Supports icons for visual clarity
│           • Bold, prominent styling
│           • Example: "💾 Save All Changes"
│
└─ NO ──► Is this in a DENSE UI? (toolbar, form grid, table actions)
          │
          ├─ YES ──► Use themed ttk.Button or CompactPremiumButton
          │           • Character-based width (width=15)
          │           • Compact padding
          │           • Fits existing layouts
          │           • Example: Standard form buttons
          │
          └─ NO ──► Use PremiumButton for visual impact
                    • Secondary page actions
                    • Important workflows
                    • Example: "🔄 Refresh Data"

---

Component Details

PremiumButton

Purpose: Hero actions that deserve attention

Characteristics:

When to Use:

✅ Primary page action (1-3 per page)

✅ Actions with loading states

✅ Actions with icons

✅ Important workflows that need emphasis

When NOT to Use:

❌ Dense toolbars (buttons will be too large)

❌ Form grids (will break layout alignment)

❌ Table row actions (too prominent)

❌ Mass replacement of all buttons

Example:


from bravura.components.premium_buttons import PremiumButton

# Perfect use case: Primary save action
save_btn = PremiumButton(
    header_frame,
    text="💾 Save All Changes",
    command=self.save_project,
    style="primary",
    loading=False  # Set to True during save operation
)
save_btn.pack(side=tk.RIGHT, padx=10)

# During save operation
save_btn.set_loading(True, "Saving...")
# ... perform save ...
save_btn.set_loading(False)

Visual Impact:

---

ttk.Button (Themed)

Purpose: Standard buttons in forms, tables, toolbars

Characteristics:

When to Use:

✅ Form buttons (OK, Cancel, Apply)

✅ Toolbar actions (many buttons in row)

✅ Table row actions

✅ Dense UI layouts

✅ Secondary/tertiary actions

When NOT to Use:

❌ Primary page action (use PremiumButton)

❌ Actions needing loading states (use PremiumButton)

Example:


# Perfect use case: Form buttons
button_frame = ttk.Frame(form)

ok_btn = ttk.Button(
    button_frame,
    text="OK",
    command=self.on_ok,
    width=10
)
ok_btn.pack(side=tk.LEFT, padx=5)

cancel_btn = ttk.Button(
    button_frame,
    text="Cancel",
    command=self.on_cancel,
    width=10
)
cancel_btn.pack(side=tk.LEFT, padx=5)

# Bravura theme automatically styles these with proper colors

Visual Impact:

---

Real-World Patterns

Pattern 1: Application with Clear Hierarchy


class MyApplication:
    def create_ui(self):
        # Header with hero action
        self.header = ttk.Frame(self.root)
        self.header.pack(fill=tk.X)

        # PRIMARY ACTION - Use PremiumButton
        self.save_btn = PremiumButton(
            self.header,
            text="💾 Save Document",
            command=self.save,
            style="primary"
        )
        self.save_btn.pack(side=tk.RIGHT, padx=10)

        # TOOLBAR - Use themed ttk.Button
        self.toolbar = ttk.Frame(self.root)
        self.toolbar.pack(fill=tk.X)

        for action in ["Bold", "Italic", "Underline"]:
            btn = ttk.Button(
                self.toolbar,
                text=action,
                width=8
            )
            btn.pack(side=tk.LEFT, padx=2)

        # FORM - Use themed ttk.Button
        self.form_buttons = ttk.Frame(self.root)
        self.form_buttons.pack()

        ttk.Button(self.form_buttons, text="OK", width=10).pack(side=tk.LEFT)
        ttk.Button(self.form_buttons, text="Cancel", width=10).pack(side=tk.LEFT)

Result: Clear visual hierarchy, professional appearance, proper sizing

---

Pattern 2: Data Management Interface


class DataManager:
    def create_ui(self):
        # MAIN ACTION AREA - Use PremiumButton
        self.action_frame = ttk.Frame(self.root)

        self.process_btn = PremiumButton(
            self.action_frame,
            text="🚀 Process Data",
            command=self.process_data,
            style="primary"
        )
        self.process_btn.pack()

        # TABLE ACTIONS - Use themed ttk.Button
        self.table_actions = ttk.Frame(self.root)

        actions = ["Add", "Edit", "Delete", "Export"]
        for action in actions:
            btn = ttk.Button(
                self.table_actions,
                text=action,
                command=lambda a=action: self.handle_action(a),
                width=8
            )
            btn.pack(side=tk.LEFT, padx=2)

    def process_data(self):
        # Show loading state during processing
        self.process_btn.set_loading(True, "Processing...")

        # ... process data ...

        self.process_btn.set_loading(False)

Result: Prominent primary action, compact table controls

---

Anti-Patterns (What NOT to Do)

❌ Anti-Pattern 1: Mass Premium Replacement

DON'T:


# Replace every single button with PremiumButton
for button_config in all_buttons:
    # This creates 50+ giant premium buttons
    PremiumButton(parent, text=button_config['text'])  # Too many!

Result:

DO INSTEAD:


# Selectively enhance key actions
hero_action = PremiumButton(header, text="Save", style="primary")

# Use themed ttk.Button for everything else
other_buttons = [ttk.Button(toolbar, text=txt) for txt in actions]

---

❌ Anti-Pattern 2: Fighting Button Sizes

DON'T:


# Try to force PremiumButton into small spaces
btn = PremiumButton(
    toolbar,
    text="Ok",
    padx=1,
    pady=0,
    font=("Arial", 8),  # Trying to make it smaller
    width=50  # Pixel width, not characters!
)
# Still looks wrong!

DO INSTEAD:


# Use the right tool for the job
btn = ttk.Button(
    toolbar,
    text="Ok",
    width=10  # Character-based, fits layout
)
# Automatically themed by Bravura

---

❌ Anti-Pattern 3: No Visual Hierarchy

DON'T:


# All buttons look the same
save_btn = ttk.Button(parent, text="Save")  # Important!
cancel_btn = ttk.Button(parent, text="Cancel")  # Secondary

Result: User can't tell what's important

DO INSTEAD:


# Clear hierarchy with button types
save_btn = PremiumButton(
    parent,
    text="Save",
    style="primary"  # Stands out
)

cancel_btn = ttk.Button(
    parent,
    text="Cancel"  # Themed but secondary
)

---

Migration from Existing Applications

Step-by-Step Integration

Step 1: Assess Your Buttons


# Inventory your buttons
buttons = {
    'hero': ['Save', 'Submit', 'Publish'],  # 1-3 main actions
    'toolbar': ['Bold', 'Italic', 'Underline', ...],  # 5-20 toolbar actions
    'form': ['OK', 'Cancel', 'Apply', ...],  # Form controls
}

Step 2: Apply Bravura Theme


# Apply theme to style all ttk.Button automatically
from bravura.themes import apply_theme

apply_theme(root, theme="wigley_site")
# All ttk.Button now have Bravura colors

Step 3: Selectively Upgrade Hero Actions


# Replace ONLY hero actions with PremiumButton
from bravura.components.premium_buttons import PremiumButton

# Before:
save_btn = ttk.Button(header, text="Save", command=self.save)

# After:
save_btn = PremiumButton(
    header,
    text="💾 Save Project",
    command=self.save,
    style="primary",
    loading=False
)

Step 4: Leave Everything Else Themed


# Keep toolbar and form buttons as ttk.Button
# They're automatically styled by the theme
toolbar_btn = ttk.Button(toolbar, text="Bold", width=8)
form_btn = ttk.Button(form_frame, text="Cancel", width=10)

# These look professional with Bravura theme colors

---

Common Questions

Q: How many PremiumButtons should I use per page?

A: Typically 1-3 maximum.

More than 3 PremiumButtons dilutes the visual hierarchy.

---

Q: Can I use PremiumButton in a toolbar?

A: Only if it's the primary toolbar action.

Example:


# Toolbar with one featured action
toolbar = ttk.Frame(root)

# Featured action
PremiumButton(toolbar, text="▶ Run", style="success").pack(side=tk.LEFT, padx=5)

# Other actions
ttk.Button(toolbar, text="Stop", width=8).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar, text="Pause", width=8).pack(side=tk.LEFT, padx=2)

---

Q: Do I need to manually style ttk.Button?

A: No! Once you apply a Bravura theme, all ttk.Button widgets automatically get professional styling with Bravura colors.


from bravura.themes import apply_theme

apply_theme(root, "wigley_site")

# All ttk.Button now styled automatically
btn = ttk.Button(parent, text="Click Me")  # Looks great!

---

Q: What about dialogs and message boxes?

A: Use PremiumButton for the primary action, ttk.Button for secondary.


dialog = tk.Toplevel()

# Primary action
PremiumButton(
    dialog,
    text="Confirm Delete",
    style="danger",
    command=self.confirm_delete
).pack()

# Secondary action
ttk.Button(
    dialog,
    text="Cancel",
    command=dialog.destroy
).pack()

---

Best Practices Summary

  1. Use PremiumButton sparingly (1-3 per page)
  2. Apply Bravura theme to style all standard buttons
  3. Reserve PremiumButton for hero actions and loading states
  4. Use ttk.Button for toolbars, forms, and dense UIs
  5. Create clear visual hierarchy with button types
  6. Use tk.Button in transient dialogs to avoid lifecycle issues
  7. Don't replace all buttons with PremiumButton
  8. Don't fight button sizes - use the right component
  9. Don't create flat hierarchy - differentiate importance

---

Critical: Buttons in Transient Dialogs (Toplevel Windows)

The Issue

PremiumButton components have asynchronous loading animations that can cause TclError exceptions when used in transient dialogs (windows created with tk.Toplevel). When a dialog is destroyed, the button's animation callbacks may still attempt to access the destroyed widget.

Similarly, ttk.Button components can have inconsistent sizing in dialogs when theme changes occur, as the theme manager's global ttk.Style modifications affect all ttk widgets simultaneously.

The Solution

For transient dialogs, use tk.Button with direct configuration instead of PremiumButton or ttk.Button.

This approach provides:

Implementation Pattern


import tkinter as tk
from tkinter import ttk, font as tkfont

def show_settings_dialog(parent):
    """Settings dialog with proper button handling."""
    dialog = tk.Toplevel(parent)
    dialog.title("Settings")
    dialog.geometry("450x600")
    dialog.transient(parent)  # Dialog depends on parent
    dialog.grab_set()  # Modal dialog

    # ... dialog content ...

    # Button frame
    button_frame = ttk.Frame(dialog)
    button_frame.pack(side="bottom", fill="x", padx=20, pady=20)

    # Use tk.Button with direct configuration
    # NOT PremiumButton or ttk.Button
    button_font = tkfont.Font(family="Segoe UI", size=10, weight="bold")

    # Helper function for consistent button creation
    def create_dialog_button(parent, text, command, colors):
        btn = tk.Button(
            parent,
            text=text,
            command=command,
            font=button_font,
            width=10,
            padx=10,
            pady=5,
            bg=colors['normal_bg'],
            fg=colors['fg'],
            activebackground=colors['active_bg'],
            activeforeground=colors['fg'],
            relief='raised',
            borderwidth=1,
            cursor='hand2'
        )

        # Add hover effects
        def on_enter(e):
            btn.config(bg=colors['hover_bg'])

        def on_leave(e):
            btn.config(bg=colors['normal_bg'])

        def on_press(e):
            btn.config(bg=colors['active_bg'], relief='sunken')

        def on_release(e):
            btn.config(bg=colors['hover_bg'], relief='raised')

        btn.bind('<Enter>', on_enter)
        btn.bind('<Leave>', on_leave)
        btn.bind('<ButtonPress-1>', on_press)
        btn.bind('<ButtonRelease-1>', on_release)

        return btn

    # Color configuration
    button_colors = {
        'normal_bg': '#20C6B7',  # Teal
        'hover_bg': '#18B1A5',   # Darker teal
        'active_bg': '#159E90',  # Even darker
        'fg': '#FFFFFF'          # White text
    }

    # Create buttons
    apply_btn = create_dialog_button(
        button_frame,
        "Apply",
        lambda: apply_settings(dialog),
        button_colors
    )
    apply_btn.pack(side="right", padx=(5, 0))

    cancel_colors = button_colors.copy()
    cancel_colors.update({
        'normal_bg': '#6B7280',  # Gray
        'hover_bg': '#4B5563',   # Darker gray
        'active_bg': '#374151'   # Even darker
    })

    cancel_btn = create_dialog_button(
        button_frame,
        "Cancel",
        dialog.destroy,
        cancel_colors
    )
    cancel_btn.pack(side="right")

    dialog.wait_window()  # Wait for dialog to close

Why This Works

1. No Asynchronous Operations

2. Direct Configuration

3. Theme Independence

When to Use This Pattern

Use tk.Button in:

Use PremiumButton in:

Complete Example: Add User Dialog


def add_user_dialog(parent):
    """User input dialog with proper button handling."""
    dialog = tk.Toplevel(parent)
    dialog.title("Add New User")
    dialog.geometry("400x300")
    dialog.transient(parent)
    dialog.grab_set()

    # Form fields
    form_frame = ttk.Frame(dialog, padding=20)
    form_frame.pack(fill="both", expand=True)

    name_var = tk.StringVar()
    email_var = tk.StringVar()

    ttk.Label(form_frame, text="Name:").grid(row=0, column=0, sticky="w", pady=5)
    ttk.Entry(form_frame, textvariable=name_var).grid(row=0, column=1, sticky="ew", pady=5)

    ttk.Label(form_frame, text="Email:").grid(row=1, column=0, sticky="w", pady=5)
    ttk.Entry(form_frame, textvariable=email_var).grid(row=1, column=1, sticky="ew", pady=5)

    # Button frame with tk.Button (NOT ttk.Button or PremiumButton)
    button_frame = ttk.Frame(form_frame)
    button_frame.grid(row=2, column=0, columnspan=2, pady=(20, 0))

    button_font = tkfont.Font(family="Segoe UI", size=9, weight="bold")

    def save_user():
        # Validate and save
        if not name_var.get() or not email_var.get():
            messagebox.showerror("Error", "All fields required")
            return
        # Save logic here...
        dialog.destroy()

    # Save button (primary action)
    save_btn = tk.Button(
        button_frame,
        text="Save",
        command=save_user,
        font=button_font,
        width=15,
        bg='#20C6B7',  # Teal
        fg='#FFFFFF',
        activebackground='#18B1A5',
        activeforeground='#FFFFFF',
        relief='raised',
        borderwidth=1,
        cursor='hand2',
        padx=5,
        pady=3
    )
    save_btn.pack(side="left", padx=5)

    # Cancel button (secondary action)
    cancel_btn = tk.Button(
        button_frame,
        text="Cancel",
        command=dialog.destroy,
        font=button_font,
        width=15,
        bg='#6B7280',  # Gray
        fg='#FFFFFF',
        activebackground='#4B5563',
        activeforeground='#FFFFFF',
        relief='raised',
        borderwidth=1,
        cursor='hand2',
        padx=5,
        pady=3
    )
    cancel_btn.pack(side="left", padx=5)

Anti-Pattern: Don't Do This


# ❌ WRONG: Using PremiumButton in transient dialog
dialog = tk.Toplevel(root)

# This can cause TclError when dialog is destroyed
save_btn = PremiumButton(
    dialog,
    text="Save",
    command=save_action,
    style="primary"
)
# PremiumButton's animation may outlive the dialog!

# ❌ WRONG: Using ttk.Button in dialog
# Will be affected by global theme style changes
save_btn = ttk.Button(dialog, text="Save", command=save_action)
# Button size may change when theme switches!

Summary

For transient dialogs (tk.Toplevel):

For main application windows:

This pattern is used throughout Bravura's templates (see admin_dashboard.py SettingsDialog and add_user examples).

---

Button Color Customization

The Bravura SDK provides flexible button styling that works standalone with beautiful defaults, while allowing easy color customization when needed.

Default Colors (No Configuration Required)

Buttons work out-of-the-box with professional Wigley Studios colors:


from bravura.components import PremiumButton, CompactPremiumButton

# These buttons use beautiful teal/dark defaults automatically
button = PremiumButton(parent, text="Start Analysis", command=start)
toolbar_btn = CompactPremiumButton(toolbar, text="Refresh", command=refresh)

Default Palette:

Easy Color Customization

Override colors for your brand using either individual color parameters or theme tokens:

Method 1: Individual Color Parameters (New in v2.0.0)


# Direct color override - simplest approach for single buttons
button = PremiumButton(
    parent,
    text="Custom Button",
    command=callback,
    style="primary",
    bg_color="#4F46E5",        # Custom background color
    fg_color="#FFFFFF",        # Custom text color
    hover_color="#6366F1",     # Custom hover color
    height=30                  # Custom height in pixels
)

Available Individual Parameters:

Note: Individual color parameters take precedence over theme colors and are preserved across theme changes.

Method 2: Theme Tokens Dictionary


# Custom purple theme via theme_tokens
purple_theme = {
    "button_bg": "#4F46E5",      # Rich indigo
    "button_fg": "#ffffff",       # White text
    "hover_bg": "#6366F1",        # Lighter on hover
    "active_bg": "#3730A3",       # Darker when pressed
}

button = PremiumButton(
    parent,
    text="Custom Button",
    command=callback,
    style="primary",
    theme_tokens=purple_theme  # Apply your colors
)

Method 3: Use Theme Manager (Recommended for Multi-Theme Apps)


from bravura.themes import ProfessionalThemeManager

# Theme manager provides complete color palettes
theme_manager = ProfessionalThemeManager(root)
theme_manager.apply_theme("platinum")  # Or "ocean", "custom", etc.

# Buttons automatically use the active theme colors
button = PremiumButton(
    parent,
    text="Themed Button",
    command=callback,
    theme_tokens=theme_manager.get_current_theme_colors()
)

Choosing the Right Method:

Available Color Keys

All button colors can be customized via theme_tokens:

Key Description Default Value
`button_bg` Button background `#20C6B7` (Teal)
`button_fg` Button text color `#ffffff` (White)
`hover_bg` Background on hover `#18B1A5` (Darker teal)
`active_bg` Background when pressed `#159E90` (Even darker)
`background_secondary` Secondary button background `#131A22` (Dark)
`accent_color_1` Accent/border color `#20C6B7` (Teal)
`text_main` Main text color `#E8EEF4` (Light)
`success_color` Success button background `#22C55E` (Green)
`error_color` Danger button background `#EF4444` (Red)
`warning_color` Warning button background `#F59E0B` (Amber)

Complete Example - All Customization Methods


import tkinter as tk
from bravura.components import PremiumButton
from bravura.themes import ProfessionalThemeManager

root = tk.Tk()
root.title("Button Customization Examples")

# Option 1: Use defaults (no configuration)
default_button = PremiumButton(root, text="Default Teal", command=action1)
default_button.pack(pady=5)

# Option 2: Individual color parameters (NEW)
purple_button = PremiumButton(
    root,
    text="Custom Purple",
    command=action2,
    bg_color="#8B5CF6",      # Purple background
    fg_color="#FFFFFF",      # White text
    hover_color="#A78BFA",   # Lighter purple on hover
    height=36                # Custom height
)
purple_button.pack(pady=5)

# Option 3: Theme tokens dictionary
ocean_colors = {
    "button_bg": "#0077BE",
    "button_fg": "#ffffff",
    "hover_bg": "#0099DD",
    "active_bg": "#005A99",
}
ocean_button = PremiumButton(
    root,
    text="Ocean Blue",
    command=action3,
    theme_tokens=ocean_colors
)
ocean_button.pack(pady=5)

# Option 4: Theme manager (best for apps with multiple themes)
theme_manager = ProfessionalThemeManager(root)
theme_manager.apply_theme("ocean")

themed_button = PremiumButton(
    root,
    text="Auto-Themed",
    command=action4,
    theme_tokens=theme_manager.get_current_theme_colors()
)
themed_button.pack(pady=5)

root.mainloop()

Color Customization Best Practices

  1. For unique buttons: Use individual color parameters (bg_color, fg_color, hover_color) when each button needs distinct colors
  2. For demos/catalogs: Use individual parameters or default colors for independent button styling
  3. For single-theme apps: Pass custom theme_tokens dictionary for consistent styling
  4. For multi-theme apps: Use ProfessionalThemeManager for professional theme switching
  5. For consistency: Use the same customization method across related buttons
  6. Height customization: Use height parameter for compact layouts (e.g., height=30 for theme selector buttons)

---

New Components Available

CompactPremiumButton

A compact button component for dense UIs with premium styling:


from bravura.components import CompactPremiumButton

# Perfect for toolbars
btn = CompactPremiumButton(
    toolbar,
    text="Connect",
    width=15,  # Character-based!
    style="primary"
)
# Premium colors + compact size + proper fit

Features:

ButtonFactory

Intelligent button selection based on context:


from bravura.components import ButtonFactory

# Automatically chooses the right button type
save_btn = ButtonFactory.create(
    parent,
    text="Save",
    context="hero"  # Options: hero, toolbar, form, action, secondary
)

# Convenience methods
hero_btn = ButtonFactory.create_hero(parent, text="Save", command=save)
toolbar_btn = ButtonFactory.create_toolbar(parent, text="Refresh", width=12)
form_btn = ButtonFactory.create_form(parent, text="OK", width=10)

Context Options:

---

Need Help?

---

---

Command Preservation System

How Commands Are Preserved Across Theme Changes

When theme changes happen, Bravura ensures button commands remain functional through a sophisticated global command registry system.

The Challenge

Tkinter's ttk.Button doesn't store Python callable functions directly. It only stores Tcl command names (strings like '1538893694400my_function'), making it impossible to retrieve the original Python function from an existing button.

The Solution

Bravura implements a 3-tier command preservation system:

1. Automatic Command Interception


# When theme manager initializes, it monkey-patches ttk.Button
# to intercept all button creations and store their commands

2. Global Command Registry


# Every button command is stored in a class-level registry
_button_command_registry = {
    'tcl_command_name': python_callable_function
}

3. Multi-Tier Fallback


# Tier 1: Check _stored_command attribute (set by interceptor)
# Tier 2: Look up in global registry using Tcl command name
# Tier 3: Legacy fallback to .command attribute

What This Means for You

✅ Create buttons normally with ttk.Button

✅ Switch themes freely - commands always work

✅ Premium button replacement preserves functionality

✅ No code changes needed


# Your code - works perfectly across theme changes
btn = ttk.Button(parent, text="Save", command=save_data)

# Theme change happens
theme_manager.apply_theme("platinum")
# → Button replaced with premium version
# → save_data() still works perfectly

# Theme change again
theme_manager.apply_theme("dark")
# → Button restored to ttk.Button
# → save_data() STILL works perfectly

For technical details, see TECHNICAL_ARCHITECTURE.md.

---

Last Updated: 2025-10-07

Version: 2.0

Applies to: Bravura v2.0.0+