Complete guide to premium buttons, theme integration, and command preservation
---
| 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 |
---
┌─ 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"
---
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:
---
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:
---
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
---
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
---
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]
---
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
---
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
)
---
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
---
A: Typically 1-3 maximum.
More than 3 PremiumButtons dilutes the visual hierarchy.
---
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)
---
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!
---
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()
---
---
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.
For transient dialogs, use tk.Button with direct configuration instead of PremiumButton or ttk.Button.
This approach provides:
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
1. No Asynchronous Operations
tk.Button has no loading animations or delayed callbacks2. Direct Configuration
ttk.Style changes3. Theme Independence
✅ Use tk.Button in:
tk.Toplevel window✅ Use PremiumButton in:
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)
# ❌ 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!
For transient dialogs (tk.Toplevel):
tk.Button with direct configurationPremiumButton (async operations)ttk.Button (global style overrides)For main application windows:
PremiumButton for hero actionsttk.Button for standard actionsCompactPremiumButton for toolbarsThis pattern is used throughout Bravura's templates (see admin_dashboard.py SettingsDialog and add_user examples).
---
The Bravura SDK provides flexible button styling that works standalone with beautiful defaults, while allowing easy color customization when needed.
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:
Override colors for your brand using either individual color parameters or theme tokens:
# 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:
bg_color (str, optional): Button background color (hex format: "#RRGGBB")fg_color (str, optional): Button text color (hex format: "#RRGGBB")hover_color (str, optional): Background color on hover (hex format: "#RRGGBB")height (int, optional): Button height in pixels (e.g., 30, 36, 40)width (int, optional): Button width in charactersNote: Individual color parameters take precedence over theme colors and are preserved across theme changes.
# 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
)
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:
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) |
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()
bg_color, fg_color, hover_color) when each button needs distinct colorstheme_tokens dictionary for consistent stylingProfessionalThemeManager for professional theme switchingheight parameter for compact layouts (e.g., height=30 for theme selector buttons)---
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:
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:
hero: Creates PremiumButton (featured actions)toolbar: Creates CompactPremiumButton (toolbar actions)form: Creates CompactPremiumButton (form controls)action: Creates CompactPremiumButton (secondary actions)secondary: Creates themed ttk.Button (background actions)---
/Bravura/docs/API_REFERENCE.md/Bravura Demos and Templates/demos/---
---
When theme changes happen, Bravura ensures button commands remain functional through a sophisticated global command registry system.
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.
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
✅ 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+