or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

effects-and-animation.mdindex.mdparticles-and-sprites.mdrenderers.mdscreen-and-display.mdwidgets-and-ui.md
tile.json

widgets-and-ui.mddocs/

Widgets and User Interface

Complete widget toolkit for building text-based user interfaces with form controls, layout management, validation, focus handling, and event processing. The widget system enables creation of sophisticated interactive applications within terminal environments.

Capabilities

Frame and Layout System

The Frame and Layout classes provide the foundation for organizing widgets into structured user interfaces.

class Frame(Effect):
    """Main container for organizing widgets into user interfaces"""
    
    def __init__(self, screen, height, width, data=None, on_load=None, has_border=True, hover_focus=False, name=None, title=None, x=None, y=None, has_shadow=False, reduce_cpu=False, is_modal=False, can_scroll=True):
        """
        Initialize UI frame container.
        
        Parameters:
        - screen (Screen): Target screen for rendering
        - height (int): Frame height in characters
        - width (int): Frame width in characters
        - data (dict): Optional data dict to initialize widgets
        - on_load (callable): Optional function to call on Frame reload
        - has_border (bool): Whether to draw frame border (default: True)
        - hover_focus (bool): Whether mouse hover changes focus (default: False)
        - name (str): Optional name to identify this Frame
        - title (str): Optional frame title
        - x (int): X position (None = centered)
        - y (int): Y position (None = centered)
        - has_shadow (bool): Whether to draw drop shadow (default: False)
        - reduce_cpu (bool): Whether to reduce CPU usage (default: False)
        - is_modal (bool): Whether Frame is modal (default: False)
        - can_scroll (bool): Whether scrollbar should be available (default: True)
        """
        
    def add_layout(self, layout):
        """
        Add layout to frame for widget organization.
        
        Parameters:
        - layout (Layout): Layout container to add
        """
        
    def fix(self):
        """Finalize frame setup and prepare for display"""
        
    def find_widget(self, name):
        """
        Find widget by name within frame.
        
        Parameters:
        - name (str): Widget name to search for
        
        Returns:
        Widget or None: Found widget or None if not found
        """
        
    @property
    def data(self):
        """Dictionary of widget names to current values"""
        
    @data.setter  
    def data(self, new_data):
        """Set widget values from dictionary"""
        
    @property
    def focussed_widget(self):
        """Currently focused widget"""
        
    def reset(self):
        """Reset frame and all widgets to initial state"""

class Layout:
    """Layout manager for organizing widgets in columns"""
    
    def __init__(self, columns, fill_frame=False):
        """
        Initialize layout with column configuration.
        
        Parameters:
        - columns (list): List of column width percentages (must sum to 100)
        - fill_frame (bool): Whether layout fills entire frame
        """
        
    def add_widget(self, widget, column=0):
        """
        Add widget to specified column.
        
        Parameters:
        - widget (Widget): Widget to add to layout
        - column (int): Column index to place widget in
        """
        
    def clear_widgets(self):
        """Remove all widgets from layout"""

Input Widgets

Interactive widgets for user input including text fields, buttons, and selection controls.

# Widget Constants
FILL_FRAME = -135792468  # Fill available frame height
FILL_COLUMN = -135792467  # Fill available column height

class Widget:
    """Abstract base class for all UI widgets"""
    
    def __init__(self, name, tab_stop=True, disabled=False):
        """
        Initialize base widget.
        
        Parameters:
        - name (str): Widget identifier name
        - tab_stop (bool): Whether widget can receive focus via Tab
        - disabled (bool): Whether widget is disabled
        """
        
    @property
    def value(self):
        """Current widget value"""
        
    @value.setter
    def value(self, new_value):
        """Set widget value"""
        
    @property
    def name(self):
        """Widget name"""
        
    @property
    def disabled(self):
        """Whether widget is disabled"""
        
    @disabled.setter
    def disabled(self, new_value):
        """Set widget disabled state"""

class Text(Widget):
    """Single-line text input widget"""
    
    def __init__(self, label=None, name=None, on_change=None, validator=None, disabled=False, height=1, hide_char=None, max_length=None, readonly=False, width=0):
        """
        Initialize text input widget.
        
        Parameters:
        - label (str): Label text to display
        - name (str): Widget name for identification
        - on_change (callable): Callback function for value changes
        - validator (str): Regex pattern for input validation
        - disabled (bool): Whether widget is disabled
        - height (int): Widget height (always 1 for Text)
        - hide_char (str): Character to display instead of input (for passwords)
        - max_length (int): Maximum input length
        - readonly (bool): Whether text is read-only
        - width (int): Widget width (0 = fill available space)
        """

class TextBox(Widget):
    """Multi-line text input widget"""
    
    def __init__(self, height, label=None, name=None, on_change=None, as_string=False, line_wrap=False, parser=None, readonly=False, disabled=False, tab=None):
        """
        Initialize multi-line text widget.
        
        Parameters:
        - height (int): Widget height in lines
        - label (str): Label text to display
        - name (str): Widget name for identification
        - on_change (callable): Callback function for value changes
        - as_string (bool): Whether to return value as string vs list
        - line_wrap (bool): Whether to wrap long lines
        - parser (Parser): Text parser for formatting
        - readonly (bool): Whether text is read-only
        - disabled (bool): Whether widget is disabled
        - tab (int): Tab size for indentation
        """

class Button(Widget):
    """Clickable button widget"""
    
    def __init__(self, text, on_click, disabled=False, add_box=False, name=None):
        """
        Initialize button widget.
        
        Parameters:
        - text (str): Button label text
        - on_click (callable): Function to call when clicked
        - disabled (bool): Whether button is disabled
        - add_box (bool): Whether to draw box around button
        - name (str): Widget name for identification
        """

class CheckBox(Widget):
    """Checkbox input widget"""
    
    def __init__(self, text, label=None, name=None, on_change=None, disabled=False):
        """
        Initialize checkbox widget.
        
        Parameters:
        - text (str): Checkbox label text
        - label (str): Optional additional label
        - name (str): Widget name for identification
        - on_change (callable): Callback function for value changes
        - disabled (bool): Whether widget is disabled
        """

class RadioButtons(Widget):
    """Radio button group widget"""
    
    def __init__(self, options, label=None, name=None, on_change=None, disabled=False):
        """
        Initialize radio button group.
        
        Parameters:
        - options (list): List of (text, value) tuples for radio options
        - label (str): Group label text
        - name (str): Widget name for identification
        - on_change (callable): Callback function for selection changes
        - disabled (bool): Whether widget is disabled
        """

Selection Widgets

Widgets for choosing from lists and collections of options.

class ListBox(Widget):
    """Single-column list selection widget"""
    
    def __init__(self, height, options, label=None, name=None, on_change=None, on_select=None, disabled=False, add_scroll_bar=False):
        """
        Initialize list box widget.
        
        Parameters:
        - height (int): Widget height in lines
        - options (list): List of (text, value) tuples for options
        - label (str): Widget label text
        - name (str): Widget name for identification
        - on_change (callable): Callback for selection changes
        - on_select (callable): Callback for item selection (Enter/double-click)
        - disabled (bool): Whether widget is disabled
        - add_scroll_bar (bool): Whether to show scroll bar
        """
        
    def add_option(self, text, value=None):
        """Add new option to list"""

class MultiColumnListBox(Widget):
    """Multi-column list selection widget"""
    
    def __init__(self, height, columns, options, titles=None, label=None, name=None, on_change=None, on_select=None, disabled=False, add_scroll_bar=False):
        """
        Initialize multi-column list widget.
        
        Parameters:
        - height (int): Widget height in lines
        - columns (list): List of column width percentages
        - options (list): List of lists containing row data
        - titles (list): Column header titles
        - label (str): Widget label text
        - name (str): Widget name for identification  
        - on_change (callable): Callback for selection changes
        - on_select (callable): Callback for item selection
        - disabled (bool): Whether widget is disabled
        - add_scroll_bar (bool): Whether to show scroll bar
        """

class DropdownList(Widget):
    """Dropdown selection list widget"""
    
    def __init__(self, options, label=None, name=None, on_change=None, disabled=False):
        """
        Initialize dropdown list widget.
        
        Parameters:
        - options (list): List of (text, value) tuples for dropdown options
        - label (str): Widget label text
        - name (str): Widget name for identification
        - on_change (callable): Callback function for selection changes
        - disabled (bool): Whether widget is disabled
        """

class FileBrowser(Widget):
    """File browser widget for file selection"""
    
    def __init__(self, height, root, file_filter=None, label=None, name=None, on_select=None, on_change=None):
        """
        Initialize file browser widget.
        
        Parameters:
        - height (int): Widget height in lines
        - root (str): Root directory path for browsing
        - file_filter (str): File extension filter (e.g., "*.txt")
        - label (str): Widget label text
        - name (str): Widget name for identification
        - on_select (callable): Callback for file selection
        - on_change (callable): Callback for directory changes
        """

Date and Time Widgets

Specialized widgets for date and time input with validation.

class DatePicker(Widget):
    """Date selection widget with calendar interface"""
    
    def __init__(self, label=None, name=None, year_range=None, on_change=None, disabled=False):
        """
        Initialize date picker widget.
        
        Parameters:
        - label (str): Widget label text
        - name (str): Widget name for identification
        - year_range (tuple): (min_year, max_year) range for selection
        - on_change (callable): Callback function for date changes
        - disabled (bool): Whether widget is disabled
        """

class TimePicker(Widget):
    """Time selection widget with hour/minute controls"""
    
    def __init__(self, label=None, name=None, seconds=False, on_change=None, disabled=False):
        """
        Initialize time picker widget.
        
        Parameters:
        - label (str): Widget label text
        - name (str): Widget name for identification
        - seconds (bool): Whether to include seconds selection
        - on_change (callable): Callback function for time changes
        - disabled (bool): Whether widget is disabled
        """

Display Widgets

Non-interactive widgets for displaying information and organizing layout.

class Label(Widget):
    """Text label display widget"""
    
    def __init__(self, text, height=1, align="<", name=None):
        """
        Initialize label widget.
        
        Parameters:
        - text (str): Label text to display
        - height (int): Widget height in lines
        - align (str): Text alignment ("<", ">", "^" for left, right, center)
        - name (str): Widget name for identification
        """

class Divider(Widget):
    """Horizontal divider line widget"""
    
    def __init__(self, height=1, line_char=None, name=None):
        """
        Initialize horizontal divider.
        
        Parameters:
        - height (int): Divider height (usually 1)
        - line_char (str): Character to use for line (None = default)
        - name (str): Widget name for identification
        """

class VerticalDivider(Widget):
    """Vertical divider line widget"""
    
    def __init__(self, height=1, name=None):
        """
        Initialize vertical divider.
        
        Parameters:
        - height (int): Divider height in lines
        - name (str): Widget name for identification
        """

Dialog Widgets

Modal dialog and popup widgets for user interaction and information display.

class PopUpDialog(Frame):
    """Modal popup dialog widget"""
    
    def __init__(self, screen, text, buttons, has_shadow=False, theme=None):
        """
        Initialize popup dialog.
        
        Parameters:
        - screen (Screen): Target screen
        - text (str): Dialog message text
        - buttons (list): List of button labels
        - has_shadow (bool): Whether dialog has drop shadow
        - theme (str): Color theme for dialog
        """

class PopupMenu(PopUpDialog):
    """Popup context menu widget"""
    
    def __init__(self, screen, menu_items, x, y, has_shadow=False):
        """
        Initialize popup menu.
        
        Parameters:
        - screen (Screen): Target screen
        - menu_items (list): List of (text, function) tuples for menu items  
        - x (int): Menu X position
        - y (int): Menu Y position
        - has_shadow (bool): Whether menu has drop shadow
        """

Usage Examples

Basic Form Creation

from asciimatics.widgets import Frame, Layout, Text, Button, Label
from asciimatics.scene import Scene
from asciimatics.screen import ManagedScreen
from asciimatics.exceptions import StopApplication

class ContactForm(Frame):
    def __init__(self, screen):
        super(ContactForm, self).__init__(screen,
                                         screen.height * 2 // 3,
                                         screen.width * 2 // 3,
                                         hover_focus=True,
                                         title="Contact Details")
        
        # Create layout
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        
        # Add form fields
        layout.add_widget(Label("Contact Information"))
        layout.add_widget(Text("Name:", "name"))
        layout.add_widget(Text("Email:", "email", validator=r".*@.*\..*"))
        layout.add_widget(Text("Phone:", "phone"))
        
        # Add buttons
        layout2 = Layout([50, 50])
        self.add_layout(layout2)
        layout2.add_widget(Button("Save", self._save), 0)
        layout2.add_widget(Button("Cancel", self._cancel), 1)
        
        self.fix()
    
    def _save(self):
        # Access form data
        data = self.data
        print(f"Saved: {data}")
        raise StopApplication("Contact saved")
    
    def _cancel(self):
        raise StopApplication("Cancelled")

def demo(screen):
    screen.play([Scene([ContactForm(screen)], -1)], stop_on_resize=True)

ManagedScreen(demo).run()

Multi-Column List with Selection

from asciimatics.widgets import Frame, Layout, MultiColumnListBox, Button
from asciimatics.scene import Scene
from asciimatics.screen import ManagedScreen

class DataViewer(Frame):
    def __init__(self, screen):
        super(DataViewer, self).__init__(screen,
                                        screen.height * 3 // 4,
                                        screen.width * 3 // 4,
                                        title="Data Viewer")
        
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        
        # Sample data
        data = [
            ["John Doe", "30", "Engineer"],
            ["Jane Smith", "25", "Designer"], 
            ["Bob Johnson", "35", "Manager"]
        ]
        
        # Create multi-column list
        self._list = MultiColumnListBox(
            height=10,
            columns=[40, 20, 40],  # Column widths as percentages
            options=data,
            titles=["Name", "Age", "Role"],
            name="data_list",
            on_select=self._on_select,
            add_scroll_bar=True
        )
        layout.add_widget(self._list)
        
        # Add buttons
        layout2 = Layout([50, 50])
        self.add_layout(layout2)
        layout2.add_widget(Button("Select", self._select), 0)
        layout2.add_widget(Button("Close", self._close), 1)
        
        self.fix()
    
    def _on_select(self):
        selected = self._list.value
        # Handle selection
        
    def _select(self):
        selected = self._list.value
        print(f"Selected row: {selected}")
        
    def _close(self):
        raise StopApplication("Closed")

def demo(screen):
    screen.play([Scene([DataViewer(screen)], -1)], stop_on_resize=True)

ManagedScreen(demo).run()

File Browser Dialog

from asciimatics.widgets import Frame, Layout, FileBrowser, Button
from asciimatics.scene import Scene
from asciimatics.screen import ManagedScreen
import os

class FileSelector(Frame):
    def __init__(self, screen):
        super(FileSelector, self).__init__(screen,
                                          screen.height * 3 // 4,
                                          screen.width * 3 // 4,
                                          title="Select File")
        
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        
        # File browser
        self._browser = FileBrowser(
            height=15,
            root=os.getcwd(),
            file_filter="*.py",  # Only show Python files
            name="file_browser",
            on_select=self._on_file_select
        )
        layout.add_widget(self._browser)
        
        # Buttons
        layout2 = Layout([50, 50])
        self.add_layout(layout2)
        layout2.add_widget(Button("Open", self._open), 0)
        layout2.add_widget(Button("Cancel", self._cancel), 1)
        
        self.fix()
    
    def _on_file_select(self):
        selected_file = self._browser.value
        print(f"File selected: {selected_file}")
    
    def _open(self):
        selected_file = self._browser.value
        if selected_file:
            print(f"Opening: {selected_file}")
        raise StopApplication("File selected")
    
    def _cancel(self):
        raise StopApplication("Cancelled")

def demo(screen):
    screen.play([Scene([FileSelector(screen)], -1)], stop_on_resize=True)

ManagedScreen(demo).run()

Form Validation Example

from asciimatics.widgets import Frame, Layout, Text, CheckBox, Button
from asciimatics.exceptions import InvalidFields, StopApplication

class ValidatedForm(Frame):
    def __init__(self, screen):
        super(ValidatedForm, self).__init__(screen,
                                           screen.height * 2 // 3,
                                           screen.width * 2 // 3,
                                           title="Registration Form")
        
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        
        # Form fields with validation
        layout.add_widget(Text("Username:", "username", validator=r"^[a-zA-Z0-9_]{3,20}$"))
        layout.add_widget(Text("Email:", "email", validator=r"^[^@]+@[^@]+\.[^@]+$"))
        layout.add_widget(Text("Password:", "password", hide_char="*"))
        layout.add_widget(CheckBox("I agree to terms", "terms"))
        
        # Buttons
        layout2 = Layout([50, 50])
        self.add_layout(layout2)
        layout2.add_widget(Button("Register", self._register), 0)
        layout2.add_widget(Button("Cancel", self._cancel), 1)
        
        self.fix()
    
    def _register(self):
        try:
            # Validate all fields
            data = self.data
            
            # Custom validation
            if not data["terms"]:
                raise InvalidFields(["terms"])
            
            if len(data["password"]) < 6:
                raise InvalidFields(["password"])
            
            print(f"Registration successful: {data['username']}")
            raise StopApplication("Registered")
            
        except InvalidFields as e:
            # Handle validation errors
            print(f"Validation failed for fields: {e.fields}")
    
    def _cancel(self):
        raise StopApplication("Cancelled")

def demo(screen):
    screen.play([Scene([ValidatedForm(screen)], -1)], stop_on_resize=True)

ManagedScreen(demo).run()