Tkinter often gets a bad reputation for looking outdated β but with the right tools and structure, you can build modern, feature-rich desktop apps using pure Python.
In this article, weβll build MateTools β Alarm Clock, a full-featured alarm clock with:
Multiple alarms
Recurring schedules
Snooze functionality
Time zone support
Live countdown timers
Dark / Light mode
A clean tabbed UI
All using Tkinter, ttk, and sv_ttk.
π Features Overview
β° Multiple alarms with custom labels
π Recurring alarms: Once, Daily, Weekdays, Weekends
π΄ Snooze support
π Time zone aware alarms (via pytz)
β³ Live countdown timers per alarm
π¨ Dark / Light mode toggle
π Active alarms dashboard
π§΅ Threaded alarm engine (non-blocking UI)
π§° Requirements
Install the required packages:
pip install sv-ttk pytz
winsound is Windows-only. This app is designed for Windows.
π§ Architecture Highlights
Threading is used so alarms donβt freeze the UI
Dictionary-based alarm objects store state cleanly
pytz ensures accurate cross-timezone alarms
sv_ttk modernizes Tkinterβs look instantly
π§© Full Source Code
β
This is the complete working application
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import datetime
import threading
import winsound
import pytz
# =========================
# App Setup
# =========================
root = tk.Tk()
root.title("MateTools - Alarm Clock")
root.geometry("950x620")
sv_ttk.set_theme("light")
# =========================
# Globals
# =========================
alarms = []
dark_mode_var = tk.BooleanVar(value=False)
timezone_var = tk.StringVar(value="UTC")
# =========================
# Helpers
# =========================
def set_status(msg):
status_var.set(msg)
root.update_idletasks()
def toggle_theme():
if dark_mode_var.get():
sv_ttk.set_theme("dark")
set_status("Theme switched to Dark mode")
else:
sv_ttk.set_theme("light")
set_status("Theme switched to Light mode")
# =========================
# Status Bar
# =========================
status_var = tk.StringVar(value="Ready")
ttk.Label(root, textvariable=status_var, anchor="w",
font=("Segoe UI", 10)).pack(side=tk.BOTTOM, fill="x")
# =========================
# Alarm Logic
# =========================
def alarm_thread(alarm):
tz = pytz.timezone(timezone_var.get())
while alarm["running"]:
now = datetime.datetime.now(tz)
weekday = now.weekday()
alarm_time = datetime.datetime.strptime(
alarm["time"], "%H:%M:%S"
).replace(year=now.year, month=now.month, day=now.day)
alarm_time = tz.localize(alarm_time)
if alarm_time < now:
alarm_time += datetime.timedelta(days=1)
remaining = alarm_time - now
h, r = divmod(int(remaining.total_seconds()), 3600)
m, s = divmod(r, 60)
alarm["remaining_var"].set(f"{h:02d}:{m:02d}:{s:02d}")
if now.strftime("%H:%M:%S") == alarm["time"]:
if (
alarm["recurring"] == "Once"
or alarm["recurring"] == "Daily"
or alarm["recurring"] == "Weekdays" and weekday < 5
or alarm["recurring"] == "Weekends" and weekday >= 5
):
winsound.Beep(1000, 1000)
snooze = messagebox.askyesno(
"Alarm", f"{alarm['label']} - Time's up!\nSnooze?"
)
if snooze:
future = now + datetime.timedelta(minutes=alarm["snooze"])
alarm["time"] = future.strftime("%H:%M:%S")
else:
if alarm["recurring"] == "Once":
alarm["running"] = False
refresh_alarm_list()
threading.Event().wait(0.5)
# =========================
# Alarm Management
# =========================
def refresh_alarm_list():
for w in alarm_list_frame.winfo_children():
w.destroy()
for i, alarm in enumerate(alarms):
row = ttk.Frame(alarm_list_frame)
row.pack(fill="x", pady=3)
ttk.Label(row, text=f"{alarm['label']} ({alarm['recurring']})",
font=("Segoe UI", 12)).pack(side="left", padx=5)
ttk.Label(row, textvariable=alarm["remaining_var"],
font=("Consolas", 12, "bold")).pack(side="left", padx=10)
ttk.Button(row, text="Stop",
command=lambda i=i: stop_alarm(i)).pack(side="right", padx=5)
ttk.Button(row, text="Delete",
command=lambda i=i: delete_alarm(i)).pack(side="right", padx=5)
def add_alarm():
try:
datetime.datetime.strptime(alarm_time_var.get(), "%H:%M:%S")
except ValueError:
messagebox.showerror("Error", "Time format must be HH:MM:SS")
return
alarm = {
"time": alarm_time_var.get(),
"label": alarm_label_var.get(),
"recurring": alarm_recurring_var.get(),
"snooze": alarm_snooze_var.get(),
"running": True,
"remaining_var": tk.StringVar(value="00:00:00")
}
t = threading.Thread(target=alarm_thread, args=(alarm,), daemon=True)
alarm["thread"] = t
alarms.append(alarm)
t.start()
refresh_alarm_list()
set_status(f"Alarm '{alarm['label']}' added")
def stop_alarm(i):
alarms[i]["running"] = False
refresh_alarm_list()
def delete_alarm(i):
alarms[i]["running"] = False
alarms.pop(i)
refresh_alarm_list()
# =========================
# UI Tabs
# =========================
tabs = ttk.Notebook(root)
tabs.pack(expand=True, fill="both", padx=20, pady=20)
# Dashboard
dash = ttk.Frame(tabs, padding=20)
tabs.add(dash, text="π Dashboard")
ttk.Label(dash, text="MateTools - Alarm Clock",
font=("Segoe UI", 20, "bold")).pack(anchor="w")
ttk.Label(dash,
text="A modern productivity alarm clock built with Python.",
font=("Segoe UI", 14)).pack(anchor="w", pady=10)
# Set Alarm
set_tab = ttk.Frame(tabs, padding=20)
tabs.add(set_tab, text="β° Set Alarm")
alarm_time_var = tk.StringVar(value="07:00:00")
alarm_label_var = tk.StringVar(value="Alarm")
alarm_recurring_var = tk.StringVar(value="Once")
alarm_snooze_var = tk.IntVar(value=5)
frame = ttk.LabelFrame(set_tab, text="Alarm Settings", padding=20)
frame.pack(fill="x")
fields = [
("Time (HH:MM:SS)", alarm_time_var),
("Label", alarm_label_var),
]
for i, (label, var) in enumerate(fields):
ttk.Label(frame, text=label).grid(row=i, column=0, sticky="e", padx=10, pady=6)
ttk.Entry(frame, textvariable=var, width=30).grid(row=i, column=1, padx=10)
ttk.Label(frame, text="Recurring").grid(row=2, column=0, sticky="e")
ttk.Combobox(frame, textvariable=alarm_recurring_var,
values=["Once", "Daily", "Weekdays", "Weekends"],
state="readonly").grid(row=2, column=1)
ttk.Label(frame, text="Snooze (minutes)").grid(row=3, column=0, sticky="e")
ttk.Entry(frame, textvariable=alarm_snooze_var).grid(row=3, column=1)
ttk.Label(frame, text="Time Zone").grid(row=4, column=0, sticky="e")
ttk.Combobox(frame, textvariable=timezone_var,
values=pytz.all_timezones,
state="readonly").grid(row=4, column=1)
ttk.Button(frame, text="Add Alarm",
command=add_alarm).grid(row=5, column=0, columnspan=2, pady=15)
ttk.Checkbutton(set_tab, text="Dark Mode",
variable=dark_mode_var,
command=toggle_theme).pack(pady=10)
# Active Alarms
active_tab = ttk.Frame(tabs, padding=20)
tabs.add(active_tab, text="π Active Alarms")
alarm_list_frame = ttk.Frame(active_tab)
alarm_list_frame.pack(fill="both", expand=True)
root.mainloop()
π§ͺ Possible Improvements
Cross-platform sound support (playsound)
Persistent alarms (save to file)
System tray minimization
Notification API integration
macOS / Linux compatibility
π About MateTools
MateTools builds practical, clean, and secure desktop tools designed for everyday productivity.



Top comments (0)