How to Create a Collapsible Sidebar in Python Tkinter
In this Python Tutorial we will see How to Create a wooden chess board using Python's built-in Tkinter library.
What We Are Gonna Use In This Project:
- Python Programming Language.- Tkinter (GUI).
- VS Editor.
- VS Editor.
Project Source Code:
import tkinter as tk
import time
import math
class ModernSidebar(tk.Frame):
"""
A modern, animated sidebar widget for tkinter applications.
"""
def __init__(self, parent):
# Initialize the parent Frame class
super().__init__(parent)
# ===== SIDEBAR DIMENSIONS =====
# These control how wide the sidebar is when expanded/collapsed
self.EXPANDED_WIDTH = 280 # Width when sidebar is open
self.COLLAPSED_WIDTH = 70 # Width when sidebar is closed
# ===== ANIMATION SETTINGS =====
# Controls how smooth the expand/collapse animation looks
self.ANIMATION_DURATION = 300 # Animation time in milliseconds
# ===== COLOR SCHEME =====
self.PRIMARY_COLOR = "#1f2937" # Main dark background
self.SECONDARY_COLOR = "#111827" # Slightly darker shade
self.ACCENT_COLOR = "#10b981" # Green accent color
self.HOVER_COLOR = "#374151" # Color when hovering over buttons
self.TEXT_COLOR = "#f3f4f6" # Light text color
self.CONTENT_BG = "#f9fafb" # Light background for main content
# ===== STATE VARIABLES =====
# These keep track of the sidebar's current state
self.is_collapsed = False # Is the sidebar currently collapsed?
self.current_width = self.EXPANDED_WIDTH # Current width of sidebar
self.animating = False # Is an animation currently running?
self.active_button = None # Which menu button is currently selected
# Set up the user interface
self.setup_ui()
def setup_ui(self):
"""
Creates all the visual elements of the sidebar.
This is like building the skeleton of our interface.
"""
# Configure the main frame (the container for everything)
self.configure(bg=self.PRIMARY_COLOR)
# ===== CREATE THE SIDEBAR CONTAINER =====
# This frame holds all the sidebar content (logo, menu, buttons)
self.sidebar = tk.Frame(
self,
bg=self.PRIMARY_COLOR,
width=self.EXPANDED_WIDTH,
highlightthickness=0 # Remove ugly border
)
self.sidebar.pack(side="left", fill="y")
self.sidebar.pack_propagate(False) # Don't let children resize this frame
# ===== CREATE THE LOGO SECTION =====
self.create_logo_section()
# ===== CREATE A SEPARATOR LINE =====
# This adds a subtle line between logo and menu
separator = tk.Frame(self.sidebar, height=1, bg="#374151")
separator.pack(fill="x", padx=15, pady=(0, 15))
# ===== CREATE THE MENU SECTION =====
self.create_menu_section()
# ===== CREATE THE COLLAPSE BUTTON =====
self.create_collapse_button()
# ===== CREATE THE MAIN CONTENT AREA =====
self.create_content_area()
def create_logo_section(self):
"""
Creates the logo area at the top of the sidebar.
Shows full text when expanded, initials when collapsed.
"""
# Container for logo elements
self.logo_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
self.logo_frame.pack(fill="x", padx=15, pady=(25, 20))
# Full logo text (shown when expanded)
self.logo_text = tk.Label(
self.logo_frame,
text="Modern Sidebar",
font=("Arial", 20, "bold"),
fg=self.ACCENT_COLOR,
bg=self.PRIMARY_COLOR
)
self.logo_text.pack(anchor="w") # Align to left (west)
# Short logo (shown when collapsed)
self.logo_icon = tk.Label(
self.logo_frame,
text="MS",
font=("Arial", 22, "bold"),
fg=self.ACCENT_COLOR,
bg=self.PRIMARY_COLOR
)
# Note: logo_icon is created but not packed yet - it will show when collapsed
def create_menu_section(self):
"""
Creates the menu buttons section of the sidebar.
Each button has an icon and text label.
"""
# Container for all menu buttons
self.menu_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
self.menu_frame.pack(fill="both", expand=True, padx=10, pady=5)
# ===== DEFINE MENU ITEMS =====
# Each item has: (display_text, icon_function, tooltip_text)
self.menu_items = [
("Dashboard", self.create_dashboard_icon, "View your dashboard"),
("Menu", self.create_menu_icon, "Manage menu items"),
("Orders", self.create_orders_icon, "Track orders"),
("Customers", self.create_customers_icon, "Customer management"),
("Analytics", self.create_analytics_icon, "View analytics"),
("Inventory", self.create_inventory_icon, "Manage inventory"),
("Marketing", self.create_marketing_icon, "Marketing campaigns"),
("Settings", self.create_settings_icon, "System settings")
]
# ===== CREATE MENU BUTTONS =====
# Store all buttons in a list so we can manage them later
self.menu_buttons = []
# Loop through each menu item and create a button for it
for i, (text, icon_func, tooltip) in enumerate(self.menu_items):
button = self.create_menu_button(text, icon_func, tooltip, i)
self.menu_buttons.append(button)
# Make the first button active by default
self.set_active_button(self.menu_buttons[0], 0)
def create_menu_button(self, text, icon_func, tooltip, index):
"""
Creates a single menu button with icon and text.
Args:
text: The text to display on the button
icon_func: Function that draws the icon
tooltip: Text to show when hovering (when collapsed)
index: Position of this button in the menu
"""
# ===== CREATE BUTTON CONTAINER =====
button_frame = tk.Frame(self.menu_frame, bg=self.PRIMARY_COLOR, height=52)
button_frame.pack(fill="x", pady=5)
button_frame.pack_propagate(False)
# ===== CREATE ACTIVE INDICATOR =====
# This colored bar shows which button is currently selected
indicator = tk.Frame(button_frame, width=4, bg=self.PRIMARY_COLOR)
indicator.pack(side="left", fill="y")
# ===== CREATE BUTTON CONTENT CONTAINER =====
content = tk.Frame(button_frame, bg=self.PRIMARY_COLOR)
content.pack(side="left", fill="both", expand=True, padx=8, pady=8)
# ===== CREATE ICON =====
# Canvas allows us to draw custom icons
icon_canvas = tk.Canvas(
content,
width=24,
height=28,
bg=self.PRIMARY_COLOR,
highlightthickness=0, # Remove border
bd=0 # Remove border
)
icon_canvas.pack(side="left", padx=(5, 10))
# Draw the icon using the provided function
icon_func(icon_canvas, self.TEXT_COLOR)
# ===== CREATE TEXT LABEL =====
text_label = tk.Label(
content,
text=text,
font=("Arial", 11),
fg=self.TEXT_COLOR,
bg=self.PRIMARY_COLOR,
anchor="w" # Align text to left
)
text_label.pack(side="left", fill="x", expand=True)
# ===== STORE REFERENCES =====
# Store important parts in the frame so we can access them later
button_frame.indicator = indicator
button_frame.icon_canvas = icon_canvas
button_frame.text_label = text_label
button_frame.content = content
button_frame.index = index
button_frame.icon_func = icon_func
# ===== BIND CLICK EVENTS =====
# Make all parts of the button clickable
for widget in [button_frame, content, icon_canvas, text_label]:
widget.bind("<Button-1>", lambda e, f=button_frame,
i=index: self.on_button_click(f, i))
widget.bind("<Enter>", lambda e, f=button_frame: self.on_button_hover(f))
widget.bind("<Leave>", lambda e, f=button_frame: self.on_button_leave(f))
pass
# Add tooltip for collapsed state
self.create_tooltip(button_frame, tooltip)
return button_frame
def create_tooltip(self, widget, text):
"""
Creates a tooltip that appears when hovering over buttons in collapsed mode.
"""
def show_tooltip(event):
# Only show tooltip when sidebar is collapsed
if self.is_collapsed:
# Calculate position for tooltip
x = widget.winfo_rootx() + 75
y = widget.winfo_rooty() + 20
# Create tooltip window
self.tooltip = tk.Toplevel(widget)
self.tooltip.wm_overrideredirect(True) # Remove window decorations
self.tooltip.wm_geometry(f"+{x}+{y}")
# Create tooltip content
tooltip_frame = tk.Frame(self.tooltip, bg=self.SECONDARY_COLOR)
tooltip_frame.pack()
label = tk.Label(
tooltip_frame,
text=text,
bg=self.SECONDARY_COLOR,
fg=self.TEXT_COLOR,
font=("Arial", 10)
)
label.pack(padx=10, pady=6)
def hide_tooltip(event):
# Remove tooltip if it exists
if hasattr(self, 'tooltip'):
self.tooltip.destroy()
# Bind hover events
widget.bind('<Enter>', show_tooltip)
widget.bind('<Leave>', hide_tooltip)
def create_collapse_button(self):
"""
Creates the button that collapses/expands the sidebar.
"""
# Container for the collapse button
bottom_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
bottom_frame.pack(side="bottom", fill="x", pady=15)
# The actual collapse button
self.collapse_button = tk.Button(
bottom_frame,
text="◄", # Left arrow when expanded
font=("Arial", 12),
fg=self.TEXT_COLOR,
bg=self.SECONDARY_COLOR,
activebackground=self.ACCENT_COLOR, # Color when clicked
activeforeground="#ffffff",
bd=0, # No border
width=6,
height=2,
cursor="hand2", # Hand cursor on hover
relief="flat", # Flat appearance
command=self.toggle_sidebar # Function to call when clicked
)
self.collapse_button.pack(fill="x")
def toggle_sidebar(self):
"""
Expands or collapses the sidebar with smooth animation.
This is the main function that creates the sliding effect.
"""
# Don't start new animation if one is already running
if self.animating:
return
# Mark that animation is starting
self.animating = True
# Determine target width and calculate animation parameters
target_width = self.COLLAPSED_WIDTH \
if not self.is_collapsed \
else self.EXPANDED_WIDTH
start_width = self.current_width
width_difference = target_width - start_width
start_time = time.time()
# Switch logo display immediately
if not self.is_collapsed:
# Going to collapsed state - show icon only
self.logo_text.pack_forget()
self.logo_icon.pack(anchor="center")
else:
# Going to expanded state - show full text
self.logo_icon.pack_forget()
self.logo_text.pack(anchor="w")
def animate_step():
"""
This function runs repeatedly to create the animation effect.
It's called every 16 milliseconds.
"""
# Calculate how much time has passed
elapsed_time = time.time() - start_time
# Check if animation is complete
if elapsed_time >= self.ANIMATION_DURATION / 1000:
# Animation finished - set final state
self.update_sidebar_width(target_width)
self.is_collapsed = not self.is_collapsed
self.collapse_button.configure(text="►" if self.is_collapsed else "◄")
self.update_button_layout()
self.animating = False
return
# Calculate animation progress (0 to 1)
progress = elapsed_time / (self.ANIMATION_DURATION / 1000)
# Apply easing for smoother animation
eased_progress = self.ease_out_cubic(progress)
# Calculate new width and update sidebar
new_width = start_width + (width_difference * eased_progress)
self.update_sidebar_width(new_width)
# Schedule next animation step
self.after(16, animate_step)
# Start the animation
animate_step()
def ease_out_cubic(self, x):
"""
Easing function that makes animation start fast and slow down.
This creates a more natural, smooth animation effect.
"""
return 1 - math.pow(1 - x, 3)
def update_sidebar_width(self, width):
"""
Updates the actual width of the sidebar.
"""
self.current_width = width
self.sidebar.configure(width=int(width))
def update_button_layout(self):
"""
Updates the layout of menu buttons when sidebar is collapsed/expanded.
In collapsed mode, only icons are shown. In expanded mode, both icons and text.
"""
for button in self.menu_buttons:
if self.is_collapsed:
# Hide text and center the icon
button.text_label.pack_forget()
button.icon_canvas.pack_forget()
button.icon_canvas.pack(side="top", padx=0, pady=(10, 0),
anchor="center")
else:
# Show both icon and text
button.icon_canvas.pack_forget()
button.icon_canvas.pack(side="left", padx=(5, 10))
button.text_label.pack(side="left", fill="x", expand=True)
# Restore active button state
if self.active_button:
self.set_active_button(self.active_button, self.active_button.index)
def create_content_area(self):
"""
Creates the main content area next to the sidebar.
This is where the actual application content would go.
"""
# Main content container
self.content_frame = tk.Frame(self, bg=self.CONTENT_BG)
self.content_frame.pack(side="left", fill="both", expand=True)
# Sample header for the content area
header = tk.Frame(self.content_frame, bg=self.CONTENT_BG, height=70)
header.pack(fill="x", padx=20, pady=10)
header.pack_propagate(False)
# Welcome message
welcome_label = tk.Label(
header,
text="Welcome Back - Application Dashboard",
font=("Arial", 24, "bold"),
fg="#111827",
bg=self.CONTENT_BG
)
welcome_label.pack(anchor="w", pady=10)
# ===== ICON DRAWING FUNCTIONS =====
# These functions draw simple icons using canvas drawing methods
def create_dashboard_icon(self, canvas, color):
"""Draws a simple house/dashboard icon"""
# House base
canvas.create_rectangle(7, 14, 17, 24, outline=color, width=2)
# House roof
canvas.create_polygon(3, 14, 12, 5, 21, 14, fill="", outline=color, width=2)
def create_menu_icon(self, canvas, color):
"""Draws three horizontal lines (hamburger menu)"""
for y in [8, 14, 20]:
canvas.create_line(5, y, 19, y, fill=color, width=2)
def create_orders_icon(self, canvas, color):
"""Draws a clipboard with checkmarks"""
# Clipboard
canvas.create_rectangle(6, 2, 18, 22, outline=color, width=2)
# Clip at top
canvas.create_rectangle(10, 1, 14, 4, outline=color, width=2)
# Checkmarks
canvas.create_line(8, 8, 10, 10, fill=color, width=2)
canvas.create_line(10, 10, 14, 6, fill=color, width=2)
canvas.create_line(8, 14, 10, 16, fill=color, width=2)
canvas.create_line(10, 16, 14, 12, fill=color, width=2)
def create_customers_icon(self, canvas, color):
"""Draws a simple person icon"""
# Head (circle)
canvas.create_oval(8, 4, 16, 12, outline=color, width=2)
# Body (arc)
canvas.create_arc(6, 15, 18, 25, start=0, extent=180,
outline=color, width=2, style="arc")
# Connect head to body
canvas.create_line(12, 12, 12, 15, fill=color, width=2)
def create_analytics_icon(self, canvas, color):
"""Draws a simple chart/graph"""
# Axes
canvas.create_line(4, 20, 20, 20, fill=color, width=2) # X-axis
canvas.create_line(4, 4, 4, 20, fill=color, width=2) # Y-axis
# Chart line
points = [4, 16, 7, 12, 10, 16, 13, 8, 16, 11, 20, 4]
canvas.create_line(*points, fill=color, width=2, smooth=True)
def create_inventory_icon(self, canvas, color):
# Shelf frame
canvas.create_line(4, 4, 4, 20, fill=color, width=2) # Left support
canvas.create_line(20, 4, 20, 20, fill=color, width=2) # Right support
# Shelves
canvas.create_line(4, 8, 20, 8, fill=color, width=2)
canvas.create_line(4, 14, 20, 14, fill=color, width=2)
canvas.create_line(4, 20, 20, 20, fill=color, width=2)
# Items on shelves
canvas.create_rectangle(6, 5, 9, 8, fill=color, outline="")
canvas.create_rectangle(15, 5, 18, 8, fill=color, outline="")
canvas.create_rectangle(7, 11, 10, 14, fill=color, outline="")
canvas.create_rectangle(14, 11, 17, 14, fill=color, outline="")
def create_marketing_icon(self, canvas, color):
# Marketing icon with megaphone
# Megaphone body
canvas.create_polygon(4, 14, 4, 10, 12, 6, 12, 18, 4, 14,
fill="", outline=color, width=2)
# Handle
canvas.create_rectangle(2, 10, 4, 14, outline=color, width=2, fill="")
# Sound waves
canvas.create_arc(12, 8, 18, 16, start=270, extent=180, style="arc",
outline=color, width=1)
canvas.create_arc(14, 6, 20, 18, start=270, extent=180, style="arc",
outline=color, width=1)
canvas.create_arc(16, 4, 22, 20, start=270, extent=180, style="arc",
outline=color, width=1)
def create_settings_icon(self, canvas, color):
"""Draws a gear/settings icon"""
center_x, center_y = 12, 12
# Gear teeth
for angle in range(0, 360, 45):
rad = math.radians(angle)
inner_x = center_x + 6 * math.cos(rad)
inner_y = center_y + 6 * math.sin(rad)
outer_x = center_x + 9 * math.cos(rad)
outer_y = center_y + 9 * math.sin(rad)
canvas.create_line(inner_x, inner_y, outer_x, outer_y, fill=color, width=2)
# Center circles
canvas.create_oval(8, 8, 16, 16, outline=color, width=2)
canvas.create_oval(10, 10, 14, 14, fill=color)
def set_active_button(self, button_frame, index):
"""
Makes a button the active/selected button.
Updates colors and the indicator bar.
"""
# Reset previous active button
if self.active_button:
self.active_button.configure(bg=self.PRIMARY_COLOR)
self.active_button.content.configure(bg=self.PRIMARY_COLOR)
self.active_button.text_label.configure(bg=self.PRIMARY_COLOR,
fg=self.TEXT_COLOR)
self.active_button.indicator.configure(bg=self.PRIMARY_COLOR, width=0)
# Redraw previous active icon with normal color
self.active_button.icon_canvas.delete("all")
self.active_button.icon_func(self.active_button.icon_canvas,
self.TEXT_COLOR)
# Set new active button
button_frame.configure(bg=self.PRIMARY_COLOR)
button_frame.content.configure(bg=self.PRIMARY_COLOR)
button_frame.text_label.configure(bg=self.PRIMARY_COLOR, fg=self.ACCENT_COLOR)
button_frame.indicator.configure(bg=self.ACCENT_COLOR, width=4)
# Redraw icon with accent color
button_frame.icon_canvas.configure(bg=self.PRIMARY_COLOR)
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, self.ACCENT_COLOR)
# Update active button reference
self.active_button = button_frame
# ===== EVENT HANDLING FUNCTIONS =====
# These functions respond to user interactions
def on_button_click(self, button_frame, index):
"""
Called when a menu button is clicked.
Changes the active button and updates the display.
"""
self.set_active_button(button_frame, index)
def on_button_hover(self, button_frame):
"""
Called when mouse hovers over a button.
Changes the button's appearance to show it's interactive.
"""
# Don't change active button appearance
if button_frame == self.active_button:
return
# Change colors to show hover state
button_frame.configure(bg=self.HOVER_COLOR)
button_frame.content.configure(bg=self.HOVER_COLOR)
button_frame.icon_canvas.configure(bg=self.HOVER_COLOR)
button_frame.text_label.configure(bg=self.HOVER_COLOR, fg="#ffffff")
# Redraw icon with brighter color
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, "#ffffff")
def on_button_leave(self, button_frame):
"""
Called when mouse leaves a button.
Resets the button's appearance to normal.
"""
# Don't change active button appearance
if button_frame == self.active_button:
return
# Reset to normal colors
button_frame.configure(bg=self.PRIMARY_COLOR)
button_frame.content.configure(bg=self.PRIMARY_COLOR)
button_frame.icon_canvas.configure(bg=self.PRIMARY_COLOR)
button_frame.text_label.configure(bg=self.PRIMARY_COLOR, fg=self.TEXT_COLOR)
# Redraw icon with normal color
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, self.TEXT_COLOR)
class ModernApp(tk.Tk):
"""
Main application window that contains the sidebar.
This is the root window of our application.
"""
def __init__(self):
# Initialize the main window
super().__init__()
# ===== WINDOW CONFIGURATION =====
self.title("Modern Sidebar Demo")
self.geometry("1000x680")
self.configure(bg="#ffffff")
# ===== CREATE THE SIDEBAR =====
sidebar = ModernSidebar(self)
sidebar.pack(fill="both", expand=True)
# ===== CENTER THE WINDOW ON SCREEN =====
self.center_window()
def center_window(self):
"""
Centers the application window on the screen.
"""
# Update window to get actual size
self.update_idletasks()
# Get window dimensions
width = self.winfo_width()
height = self.winfo_height()
# Calculate center position
x = (self.winfo_screenwidth() // 2) - (width // 2)
y = (self.winfo_screenheight() // 2) - (height // 2)
# Set window position
self.geometry(f'+{x}+{y}')
# ===== MAIN PROGRAM =====
if __name__ == "__main__":
# Create and run the application
app = ModernApp()
app.mainloop() # Start the GUI event loop
The Final Result:
Download Projects Source Code







