GUI Setup, Theme, and Login Screen
GUI Setup, Theme, and Login Screen
Now you will begin building the graphical interface for your application.
Until now, everything has been backend logic.
In this lesson, you will:
- create the main window
- define a reusable theme (colors and fonts)
- build a polished login screen using a centered card
- prepare the structure for the dashboard (next lesson)
You are not connecting logic yet — only building structure.
Goal of This Lesson
By the end of this lesson, you will:
- create a Tkinter window with a clean background
- define a theme dictionary and font constants
- build a styled button helper
- build a centered login card with input fields and action buttons
Step 1: Create GUI File
Create:
app/ui/app_ui.py
Step 2: Import Tkinter
import tkinter as tk
Step 3: Define the Theme
A real application uses consistent colors and fonts everywhere.
Instead of repeating color values, store them in one place.
Add at the top of app_ui.py:
THEME = {
"bg": "#f3f4f6", # window background (light grey)
"card": "#ffffff", # card / panel background
"border": "#e5e7eb", # subtle borders
"text": "#111827", # primary text
"muted": "#6b7280", # secondary text
"primary": "#2563eb", # blue (primary action)
"success": "#16a34a", # green (positive action)
"danger": "#dc2626", # red (destructive action)
}
FONT_TITLE = ("Segoe UI", 22, "bold")
FONT_HEAD = ("Segoe UI", 14, "bold")
FONT_LABEL = ("Segoe UI", 10)
FONT_BTN = ("Segoe UI", 10, "bold")
Why a Theme Dictionary?
- one place to change colors
- consistent look across all screens
- easier to read code (
THEME["primary"]instead of"#2563eb") - prepares your project for proper UI design
Step 4: Create a Styled Button Helper
Tkinter’s default buttons look outdated.
Create a helper that returns a flat, color-coded button:
def _styled_button(parent, text, command, kind="primary"):
colors = {
"primary": (THEME["primary"], "white"),
"success": (THEME["success"], "white"),
"danger": (THEME["danger"], "white"),
"ghost": (THEME["card"], THEME["text"]),
}
bg, fg = colors[kind]
return tk.Button(
parent, text=text, command=command,
bg=bg, fg=fg, activebackground=bg, activeforeground=fg,
font=FONT_BTN, relief="flat", bd=0, padx=14, pady=6,
cursor="hand2",
)
Now any screen can call _styled_button(parent, "Login", handle_login, "primary") and get a consistent look.
Step 5: Create Main Window
def create_app():
window = tk.Tk()
window.title("Productivity App")
window.geometry("900x620")
window.minsize(820, 560)
window.configure(bg=THEME["bg"])
return window
The window is wider than before because the dashboard will use a two-column layout in the next lesson.
Step 6: Helper to Clear the Window
When switching between screens (login → dashboard → login), you need to remove the old widgets first.
def _clear(window):
for child in window.winfo_children():
child.destroy()
Step 7: Build the Login Screen
The login screen will be a centered card with:
- a title
- a subtitle
- username and password fields
- a message label (for errors / confirmations)
- Login and Create Account buttons
def show_login(window):
_clear(window)
# Outer container fills the window so we can center the card inside it.
outer = tk.Frame(window, bg=THEME["bg"])
outer.pack(fill="both", expand=True)
# The card itself — fixed size, centered with .place().
card = tk.Frame(
outer, bg=THEME["card"],
highlightbackground=THEME["border"], highlightthickness=1,
)
card.place(relx=0.5, rely=0.5, anchor="center", width=380, height=420)
# Title and subtitle
tk.Label(
card, text="Productivity App", font=FONT_TITLE,
bg=THEME["card"], fg=THEME["text"],
).pack(pady=(30, 4))
tk.Label(
card, text="Sign in to continue", font=FONT_LABEL,
bg=THEME["card"], fg=THEME["muted"],
).pack(pady=(0, 20))
# Username field
tk.Label(card, text="Username", font=FONT_LABEL,
bg=THEME["card"], fg=THEME["muted"]).pack(anchor="w", padx=40)
username_entry = tk.Entry(
card, font=FONT_LABEL, relief="flat",
bg="#f9fafb", highlightthickness=1,
highlightbackground=THEME["border"],
)
username_entry.pack(fill="x", padx=40, ipady=6, pady=(2, 12))
# Password field
tk.Label(card, text="Password", font=FONT_LABEL,
bg=THEME["card"], fg=THEME["muted"]).pack(anchor="w", padx=40)
password_entry = tk.Entry(
card, show="•", font=FONT_LABEL, relief="flat",
bg="#f9fafb", highlightthickness=1,
highlightbackground=THEME["border"],
)
password_entry.pack(fill="x", padx=40, ipady=6, pady=(2, 8))
# Message label (filled in later when login is wired up)
message_label = tk.Label(card, text="", font=FONT_LABEL,
bg=THEME["card"], fg=THEME["danger"])
message_label.pack(pady=(4, 6))
# Buttons
_styled_button(card, "Login", lambda: None, "primary").pack(
fill="x", padx=40, pady=(8, 6), ipady=4)
_styled_button(card, "Create account", lambda: None, "success").pack(
fill="x", padx=40, pady=(0, 20), ipady=4)
return card, username_entry, password_entry
The buttons use lambda: None for now. In the next lesson you will replace these with real login and registration handlers.
Why .place() for the Card?
pack()andgrid()align widgets top-to-bottom or in rows/columns..place()lets you position something at exact coordinates.relx=0.5, rely=0.5, anchor="center"puts the card’s center at the window’s center.
This is the standard way to center a “card” in Tkinter.
Step 8: Update main.py
from app.services.database import create_tables
from app.ui.app_ui import create_app, show_login
def main():
create_tables()
window = create_app()
show_login(window)
window.mainloop()
if __name__ == "__main__":
main()
Result
When you run the program, you should see:
- a window with a light grey background
- a white centered card
- “Productivity App” title
- “Sign in to continue” subtitle
- username and password fields with bullet character masking
- a blue Login button and a green Create Account button
Why This Step Matters
You are separating:
- backend logic → services
- frontend UI → ui
You are also setting up:
- a reusable theme
- helper functions
- a structure that will scale to the dashboard
This is how real applications are organized.
Important Notes
- Do not mix logic into UI yet
- Keep the theme dictionary at the top of the file
- Use
_styled_buttoneverywhere instead of rawtk.Button - Each screen should be its own function
- Always call
_clear(window)at the start of a screen function
Current Project Structure
app/
services/
database.py
user_service.py
task_service.py
expense_service.py
dashboard_service.py
ui/
app_ui.py
Summary
You created the base GUI structure with a theme, a styled button helper, and a polished centered login card.
In the next lesson you will build the dashboard layout — a header bar, colored stat cards, and side-by-side panels for tasks and expenses.