textual-event-messages
Handles keyboard, mouse, and custom events in Textual applications using messages and handlers. Use when implementing keyboard bindings, custom message passing, event bubbling, action dispatch, and inter-widget communication. Covers event handling patterns, message definitions, and routing.
What this skill does
# Textual Event and Message Handling
## Purpose
Implement robust event handling and inter-widget communication in Textual using messages, keyboard bindings, and action dispatch. Messages enable loose coupling between components.
## Quick Start
```python
from textual.message import Message
from textual.app import App, ComposeResult
from textual.widgets import Button, Static
from textual import on
class ItemWidget(Static):
"""Widget that emits custom messages."""
class ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
class MyApp(App):
def compose(self) -> ComposeResult:
yield ItemWidget()
@on(ItemWidget.ItemSelected)
def on_item_selected(self, message: ItemWidget.ItemSelected) -> None:
"""Handle item selection."""
self.notify(f"Selected: {message.item_id}")
```
## Instructions
### Step 1: Define Custom Messages
Create message classes to communicate between widgets:
```python
from textual.message import Message
from dataclasses import dataclass
# Simple message without data
class DataRefreshed(Message):
"""Posted when data is refreshed."""
pass
# Message with data
class ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str, index: int) -> None:
"""Initialize message.
Args:
item_id: Selected item ID.
index: Index in list.
"""
super().__init__()
self.item_id = item_id
self.index = index
# Message with complex data
@dataclass(frozen=True)
class SearchResult:
"""Result of search operation."""
query: str
items: list[str]
total_count: int
class SearchCompleted(Message):
"""Posted when search completes."""
def __init__(self, result: SearchResult) -> None:
"""Initialize with search result."""
super().__init__()
self.result = result
```
**Message Conventions:**
- Subclass `Message`
- Define immutable data in `__init__`
- Use descriptive PascalCase names
- Document the message purpose in docstring
- Use `frozen=True` for dataclasses to ensure immutability
### Step 2: Post and Handle Messages
Post messages from widgets and handle in parents:
```python
from textual import on
from textual.widgets import Static, ListView, ListItem
from textual.app import ComposeResult
class ItemWidget(Static):
"""Widget that posts custom messages."""
class ItemClicked(Message):
"""Posted when item is clicked."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
def __init__(self, item_id: str, label: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._item_id = item_id
self._label = label
def render(self) -> str:
return f"[{self._item_id}] {self._label}"
def on_click(self) -> None:
"""Post message when clicked."""
self.post_message(self.ItemClicked(self._item_id))
class ItemListWidget(Static):
"""Parent widget that handles item messages."""
def compose(self) -> ComposeResult:
yield ListView(id="item-list")
async def on_mount(self) -> None:
"""Create items."""
list_view = self.query_one("#item-list", ListView)
for i, item_id in enumerate(["a", "b", "c"]):
await list_view.append(ListItem(
ItemWidget(item_id, f"Item {item_id}")
))
@on(ItemWidget.ItemClicked)
async def on_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Handle item click - called automatically."""
self.notify(f"Clicked: {message.item_id}")
# Alternative handler without @on decorator
class AltListWidget(Static):
"""Using on_* method naming convention."""
def on_item_widget_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Auto-routed handler.
Convention: on_{widget_class_snake_case}_{message_class_snake_case}
"""
self.notify(f"Clicked: {message.item_id}")
```
**Message Routing:**
1. Widget posts message: `self.post_message(MyMessage())`
2. Message bubbles up to parent widgets
3. Parent handles with `@on(MessageType)` or `on_*` method
4. First handler to handle the message stops propagation (can call `event.stop()`)
### Step 3: Implement Keyboard Event Handlers
Handle keyboard input directly:
```python
from textual.events import Key, Paste
from textual.widgets import Static
class KeyboardWidget(Static):
"""Widget handling keyboard events."""
def on_key(self, event: Key) -> None:
"""Called when any key is pressed.
Args:
event: Key event with key name.
"""
key_name = event.key
# Common keys: "up", "down", "left", "right", "enter", "escape"
# Letters: "a", "b", "ctrl+a", "shift+a"
if key_name == "enter":
self.handle_enter()
elif key_name == "escape":
self.handle_escape()
elif key_name == "ctrl+c":
self.app.exit()
def handle_enter(self) -> None:
"""Handle Enter key."""
self.update("Enter pressed")
def handle_escape(self) -> None:
"""Handle Escape key."""
self.update("Escape pressed")
def on_paste(self, event: Paste) -> None:
"""Called when text is pasted.
Args:
event: Paste event with text.
"""
self.update(f"Pasted: {event.text}")
```
**Key Event Handling:**
- `on_key()` called for keyboard events
- Common keys: arrow keys, enter, escape, tab, delete
- Modifier keys: "ctrl+key", "shift+key", "alt+key"
- Special: "home", "end", "page_up", "page_down", "F1"-"F12"
### Step 4: Add Keyboard Bindings and Actions
Bindings provide discoverable keyboard shortcuts tied to actions:
```python
from textual.app import App, ComposeResult
from textual.binding import Binding
from typing import ClassVar
class MyApp(App):
"""App with keyboard bindings."""
BINDINGS: ClassVar[list[Binding]] = [
# (key, action, description, show, priority)
Binding("q", "quit", "Quit", show=True, priority=True),
Binding("ctrl+c", "quit", "Quit", show=False, priority=True),
Binding("r", "refresh", "Refresh"),
Binding("n", "new_item", "New"),
Binding("?", "show_help", "Help"),
]
def compose(self) -> ComposeResult:
yield Header()
yield Static("Content")
yield Footer() # Shows bindings
def action_refresh(self) -> None:
"""Action handler for 'refresh' binding."""
self.notify("Refreshed")
def action_new_item(self) -> None:
"""Action handler for 'new_item' binding."""
self.notify("Creating new item")
async def action_show_help(self) -> None:
"""Action handlers can be async."""
# Show help dialog
pass
```
**Binding Conventions:**
- Binding key to action method name
- Action method: `action_{action_name}`
- Action methods can be sync or async
- Bindings shown in Footer if `show=True`
- `priority=True` for high-priority bindings (quit)
- Bindings override key event handlers when matched
### Step 5: Handle Mouse Events
React to mouse clicks and movements:
```python
from textual.events import MouseDown, MouseUp, MouseMove
from textual.widgets import Static
class MouseWidget(Static):
"""Widget handling mouse events."""
def on_mouse_down(self, event: MouseDown) -> None:
"""Called when mouse button pressed.
Args:
event: MouseDown event with position and button.
"""
x, y = event.x, event.y
button = event.button # 1: left, 2: middle, 3: right
if button == 1: # Left click
self.handle_click(x, y)
def on_mouse_up(self, event: MouseUp) -> None:
"""Called when mouse button released."""
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.