Urwid

A console user interface library for Python. A well-established TUI library providing a rich widget set and event-driven programming model.

pythontuilibraryconsolewidgetsevent-driven

GitHub Overview

urwid/urwid

Console user interface library for Python (official repo)

Stars2,930
Watchers56
Forks323
Created:February 25, 2010
Language:Python
License:GNU Lesser General Public License v2.1

Topics

None

Star History

urwid/urwid Star History
Data as of: 7/25/2025, 06:23 AM

Library

Urwid

Overview

Urwid is a mature library for building console user interfaces in Python. It provides a rich widget set and event-driven programming model, enabling the development of complex TUI applications.

Details

Urwid was developed by Ian Ward in 2004 and has been a pioneering presence in Python TUI libraries, used in many projects over the years. It's highly regarded for its stability and maturity, trusted even for enterprise-level application development.

Key Features

  • Rich Widget Set: 50+ widgets including buttons, text boxes, list boxes, menus
  • Layout System: Flexible layout management
  • Event-Driven: Handling keyboard, mouse, and timer events
  • Custom Widgets: Easy creation of custom widgets
  • Theme System: Color palette configuration
  • Asynchronous Processing: Support for non-blocking operations
  • Unicode Support: Multi-language text display
  • Mouse Support: Mouse event handling

Architecture

Urwid's design consists of the following main components:

  • Widget: Base class for UI elements
  • MainLoop: Event loop and application control
  • Pile/Columns: Layout containers
  • Frame: Layout with header, footer, and body
  • Palette: Color theme management
  • Canvas: Drawing surface

Pros and Cons

Pros

  • Long track record and stability
  • Rich widget library
  • Detailed documentation
  • Flexible layout system
  • Full mouse and keyboard support
  • Relatively lightweight
  • Mature community
  • Enterprise adoption history

Cons

  • Somewhat old-fashioned API design
  • High learning cost
  • Lack of modern UI elements
  • Limited styling options
  • Slow addition of new features

Key Links

Code Example

import urwid

class TodoApp:
    def __init__(self):
        self.todos = []
        self.current_id = 0
        
        # Header
        header = urwid.Text("📝 Todo App")
        header = urwid.AttrMap(header, 'header')
        
        # Input field
        self.edit = urwid.Edit("New todo: ")
        
        # Buttons
        add_btn = urwid.Button("Add")
        urwid.connect_signal(add_btn, 'click', self.add_todo)
        
        clear_btn = urwid.Button("Clear All")
        urwid.connect_signal(clear_btn, 'click', self.clear_todos)
        
        # Button layout
        buttons = urwid.Columns([
            ('weight', 1, add_btn),
            ('weight', 1, clear_btn)
        ])
        
        # Todo list
        self.todo_list = urwid.SimpleFocusListWalker([])
        self.listbox = urwid.ListBox(self.todo_list)
        
        # Layout
        pile = urwid.Pile([
            ('pack', self.edit),
            ('pack', buttons),
            urwid.Divider(),
            self.listbox
        ])
        
        # Frame
        self.frame = urwid.Frame(
            pile,
            header=header,
            footer=urwid.Text("Enter: Add | Space: Toggle | d: Delete")
        )
    
    def add_todo(self, button):
        text = self.edit.get_edit_text().strip()
        if text:
            todo_item = TodoItem(self.current_id, text, self.on_delete)
            self.todo_list.append(todo_item)
            self.todos.append({'id': self.current_id, 'text': text, 'done': False})
            self.current_id += 1
            self.edit.set_edit_text("")
    
    def clear_todos(self, button):
        self.todo_list.clear()
        self.todos.clear()
    
    def on_delete(self, todo_id):
        # Remove corresponding Todo from list
        for i, item in enumerate(self.todo_list):
            if hasattr(item, 'todo_id') and item.todo_id == todo_id:
                del self.todo_list[i]
                break
        
        # Remove from data as well
        self.todos = [t for t in self.todos if t['id'] != todo_id]
    
    def unhandled_input(self, key):
        if key in ('q', 'Q'):
            raise urwid.ExitMainLoop()

class TodoItem(urwid.WidgetWrap):
    def __init__(self, todo_id, text, delete_callback):
        self.todo_id = todo_id
        self.done = False
        self.delete_callback = delete_callback
        
        self.checkbox = urwid.CheckBox("", state=False)
        self.text = urwid.Text(text)
        
        # Layout
        columns = urwid.Columns([
            ('fixed', 4, self.checkbox),
            self.text
        ])
        
        super().__init__(columns)
    
    def keypress(self, size, key):
        if key == ' ':
            self.checkbox.toggle_state()
            self.done = not self.done
            style = 'done' if self.done else None
            self.text.set_text(('done' if self.done else None, self.text.text))
            return None
        elif key == 'd':
            self.delete_callback(self.todo_id)
            return None
        
        return super().keypress(size, key)

def main():
    # Color palette
    palette = [
        ('header', 'white', 'dark blue', 'bold'),
        ('done', 'dark gray', 'default', 'strikethrough'),
    ]
    
    app = TodoApp()
    
    # Main loop
    loop = urwid.MainLoop(
        app.frame,
        palette=palette,
        unhandled_input=app.unhandled_input
    )
    
    loop.run()

if __name__ == "__main__":
    main()