← Back to list

cli-todo-ui
by marjan-ahmed
A 5-phase evolution of a Todo application, transitioning from a basic build to a Cloud-Native AI system using Spec-Driven Development (SDD) principles.
⭐ 0🍴 0📅 Jan 1, 2026
SKILL.md
name: cli-todo-ui description: Build modern, interactive terminal-based todo applications with beautiful UI/UX using Python's Textual framework. Use when building CLI todo apps, task managers, or interactive terminal interfaces that require menu-driven flows, visual polish (colors, icons, tables), keyboard shortcuts, mouse support, and professional developer experience. Ideal for hackathons and rapid prototyping of terminal UIs.
CLI Todo UI Builder
Build professional, interactive terminal-based todo applications with modern aesthetics using Python's Textual framework and Rich library.
Quick Start
Option 1: Generate Complete App (Recommended)
Use the complete todo app template from assets/todo-app-template/:
# Copy the template to your project
cp -r assets/todo-app-template/* ./
# Install dependencies
pip install -r requirements.txt
# Run the app
python app.py
The template includes:
- Full Textual application with menu-driven interface
- In-memory task storage (decoupled from UI)
- Color-coded status indicators (☐ Pending, ☑ Completed)
- Keyboard shortcuts and mouse support
- Professional styling with Textual CSS
Option 2: Install Dependencies Only
bash scripts/install_dependencies.sh
Core Stack
- Textual (≥0.63.0): Modern TUI framework with reactive components
- Rich (≥13.7.0): Beautiful terminal formatting
- Pydantic (≥2.0.0): Data validation (optional)
Key Features to Implement
Essential Features (Must-Have)
- Interactive DataTable: Arrow key navigation, row selection
- Color-coded statuses: Visual indicators (🔴 High, 🟡 Medium, 🟢 Low priority)
- Keyboard shortcuts: Visible in footer (
aAdd,dDelete,spaceToggle,qQuit) - Confirmation dialogs: Modal confirmations for destructive actions
- Stats panel: Live counts (Total, Pending, Completed, %)
Enhanced Features (Nice-to-Have)
- Live search/filter: Type to filter tasks instantly
- Mouse support: Click to select, drag to reorder
- Progress bars: Visual completion percentage
- Split layout: Task list (left) + Details preview (right)
- Tabbed interface: Switch between "All", "Active", "Completed"
Advanced Features (Bonus)
- Undo/Redo: Revert last action with visual feedback
- Bulk operations: Multi-select for batch delete/complete
- Export: Pretty-print to markdown/JSON
- Theme toggle: Dark/light mode switching
- Animations: Smooth transitions and task completion effects
Architecture Pattern
In-Memory Task Storage (Decoupled)
from dataclasses import dataclass
from typing import List
from datetime import datetime
@dataclass
class Task:
id: int
title: str
description: str = ""
completed: bool = False
created_at: datetime = None
def __post_init__(self):
if self.created_at is None:
self.created_at = datetime.now()
class TaskManager:
"""Business logic layer - decoupled from UI"""
def __init__(self):
self.tasks: List[Task] = []
self.next_id = 1
def add_task(self, title: str, description: str = "") -> Task:
task = Task(id=self.next_id, title=title, description=description)
self.tasks.append(task)
self.next_id += 1
return task
def get_task(self, task_id: int) -> Task | None:
return next((t for t in self.tasks if t.id == task_id), None)
def delete_task(self, task_id: int) -> bool:
task = self.get_task(task_id)
if task:
self.tasks.remove(task)
return True
return False
def toggle_task(self, task_id: int) -> bool:
task = self.get_task(task_id)
if task:
task.completed = not task.completed
return True
return False
def get_stats(self) -> dict:
total = len(self.tasks)
completed = sum(1 for t in self.tasks if t.completed)
return {
"total": total,
"completed": completed,
"pending": total - completed,
"percentage": (completed / total * 100) if total > 0 else 0
}
Textual App Structure
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal
from textual.widgets import Header, Footer, DataTable, Button, Static
from textual.binding import Binding
class TodoApp(App):
"""Main Textual application"""
CSS = """
DataTable {
height: 1fr;
border: solid $primary;
}
#stats {
height: 3;
background: $panel;
border: solid $secondary;
padding: 1;
}
"""
BINDINGS = [
Binding("a", "add_task", "Add Task"),
Binding("d", "delete_task", "Delete"),
Binding("space", "toggle_task", "Toggle"),
Binding("q", "quit", "Quit"),
]
def __init__(self):
super().__init__()
self.task_manager = TaskManager()
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield Static(id="stats")
yield DataTable(zebra_stripes=True)
yield Footer()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_columns("ID", "Status", "Title", "Description")
self.refresh_table()
def action_add_task(self) -> None:
# Implement add task modal
pass
def action_delete_task(self) -> None:
# Implement delete with confirmation
pass
def action_toggle_task(self) -> None:
# Toggle selected task
pass
def refresh_table(self) -> None:
# Update table with current tasks
pass
if __name__ == "__main__":
app = TodoApp()
app.run()
Reference Documentation
- Textual Patterns: See
references/textual-patterns.mdfor widgets, styling, and reactive patterns - UI Features: See
references/ui-features.mdfor comprehensive UI/UX enhancement examples - Keyboard Shortcuts: See
references/keyboard-shortcuts.mdfor standard binding patterns
Common Patterns
Adding Confirmation Dialogs
from textual.screen import ModalScreen
from textual.widgets import Label, Button
class ConfirmDialog(ModalScreen):
def __init__(self, message: str):
super().__init__()
self.message = message
def compose(self) -> ComposeResult:
yield Container(
Label(self.message),
Horizontal(
Button("Confirm", variant="error", id="confirm"),
Button("Cancel", variant="default", id="cancel")
)
)
# Usage in app
def action_delete_task(self) -> None:
def handle_response(confirmed: bool) -> None:
if confirmed:
# Delete task
pass
self.push_screen(ConfirmDialog("Delete this task?"), handle_response)
Live Filtering
from textual.widgets import Input
class TodoApp(App):
def compose(self) -> ComposeResult:
yield Header()
yield Input(placeholder="Search tasks...", id="search")
yield DataTable()
yield Footer()
def on_input_changed(self, event: Input.Changed) -> None:
search_term = event.value.lower()
filtered_tasks = [
t for t in self.task_manager.tasks
if search_term in t.title.lower() or search_term in t.description.lower()
]
self.refresh_table(filtered_tasks)
Status Indicators
Use these emoji/color patterns for visual feedback:
- Task Status: ☐ Pending (gray), ☑ Completed (green), ⏳ In Progress (yellow)
- Priority: 🔴 High, 🟡 Medium, 🟢 Low
- Actions: ✨ Add, 🗑️ Delete, ✓ Toggle, 🔍 Search
Testing
Test the script by running it:
python app.py
Expected behavior:
- App launches with empty task list
- Keyboard shortcuts appear in footer
- Can add, view, toggle, and delete tasks
- Stats update in real-time
- UI is visually polished with colors and borders
Troubleshooting
- Import errors: Ensure
textualandrichare installed - Terminal size: Textual requires minimum 80x24 terminal
- Colors not showing: Check terminal supports 256 colors
- Mouse not working: Enable mouse support in terminal emulator
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
3ヶ月以内に更新
+5
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
