textual
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTextual - Python TUI Framework Expert
Textual - Python TUI框架专家
You are an expert in building Text User Interface (TUI) applications using Textual, a modern Python framework for creating sophisticated terminal applications. This skill provides comprehensive guidance on Textual's architecture, best practices, and common patterns.
您是使用Textual构建文本用户界面(TUI)应用的专家,Textual是一款用于创建复杂终端应用的现代Python框架。本技能提供关于Textual架构、最佳实践和常见模式的全面指导。
What is Textual?
什么是Textual?
Textual is a TUI framework by Textualize.io that enables developers to build:
- Beautiful, responsive terminal applications
- Rich, interactive command-line tools
- Cross-platform TUIs with modern UX patterns
- Applications with CSS-like styling and reactive programming
Textual是Textualize.io推出的TUI框架,能让开发者构建:
- 美观、响应式的终端应用
- 丰富、交互式的命令行工具
- 采用现代UX模式的跨平台TUI
- 支持类CSS样式和响应式编程的应用
When to Use This Skill
何时使用本技能
Invoke this skill when the user:
- Wants to build or modify a TUI application
- Asks about Textual framework features
- Needs help with widgets, screens, or layouts
- Has questions about CSS styling in Textual
- Wants to implement reactive programming patterns
- Needs testing guidance for Textual apps
- Encounters errors or issues with Textual code
- Asks about TUI design patterns or best practices
当用户有以下需求时,调用本技能:
- 想要构建或修改TUI应用
- 询问Textual框架的功能
- 需要Widgets、Screens或布局相关帮助
- 对Textual中的CSS样式有疑问
- 想要实现响应式编程模式
- 需要Textual应用的测试指导
- 遇到Textual代码的错误或问题
- 询问TUI设计模式或最佳实践
Core Concepts
核心概念
Application Architecture
应用架构
Textual applications follow an event-driven architecture:
- The class is the entry point and foundation
App - Screens contain widgets and occupy the full terminal
- Widgets are reusable UI components managing rectangular regions
- Messages enable communication between components
- CSS (TCSS) provides styling separate from logic
Textual应用遵循事件驱动架构:
- 类是入口点和基础
App - Screens包含Widgets并占据整个终端界面
- Widgets是可复用的UI组件,管理矩形区域
- Messages实现组件间的通信
- **CSS(TCSS)**提供与逻辑分离的样式定义
Key Components
关键组件
App Class:
- Entry point via
app.run() - Manages screens, modes, and global state
- Handles key bindings and actions
- Configures CSS via or inline
CSS_PATHCSS
Screens:
- Full-terminal containers for widgets
- Support push/pop navigation stack
- Can be modal for dialogs
- Define their own key bindings and CSS
Widgets:
- Rectangular UI components
- Support composition via
compose() - Handle events via methods
on_* - Can be focused and styled with CSS
App类:
- 通过作为入口
app.run() - 管理Screens、模式和全局状态
- 处理按键绑定和动作
- 通过或内联
CSS_PATH配置样式CSS
Screens:
- 容纳Widgets的全终端容器
- 支持推送/弹出导航栈
- 可作为对话框的模态窗口
- 定义自己的按键绑定和CSS
Widgets:
- 矩形UI组件
- 通过支持组合
compose() - 通过方法处理事件
on_* - 可获取焦点并通过CSS设置样式
Reactive Programming
响应式编程
Textual's reactive system automatically updates the UI when data changes:
python
from textual.reactive import reactive
class Counter(Widget):
count = reactive(0) # Auto-refreshes on change
def render(self) -> str:
return f"Count: {self.count}"Features:
- Validation: methods constrain values
validate_<attr>() - Watchers: methods react to changes
watch_<attr>() - Computed properties: for derived values
compute_<attr>() - Recompose: Rebuild widget tree when data changes
Textual的响应式系统会在数据变化时自动更新UI:
python
from textual.reactive import reactive
class Counter(Widget):
count = reactive(0) # 变化时自动刷新
def render(self) -> str:
return f"Count: {self.count}"特性:
- 验证:方法约束值
validate_<attr>() - 监听器:方法响应变化
watch_<attr>() - 计算属性:用于派生值
compute_<attr>() - 重新组合:数据变化时重建Widget树
CSS Styling (TCSS)
CSS样式(TCSS)
Textual uses CSS-like syntax for styling:
css
Button {
background: $primary;
margin: 1;
}
#submit-button {
background: $success;
}
.danger {
background: $error;
}Benefits:
- Separation of concerns (style vs logic)
- Live reload during development
- Theme system with semantic colors
- Responsive layout with FR units
Textual使用类CSS语法进行样式定义:
css
Button {
background: $primary;
margin: 1;
}
#submit-button {
background: $success;
}
.danger {
background: $error;
}优势:
- 关注点分离(样式与逻辑分开)
- 开发期间支持实时重载
- 带有语义颜色的主题系统
- 使用FR单位实现响应式布局
Common Patterns
常见模式
Basic App Template
基础应用模板
python
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static
class MyApp(App):
CSS_PATH = "app.tcss"
def compose(self) -> ComposeResult:
yield Header()
yield Static("Hello, Textual!")
yield Footer()
def on_mount(self) -> None:
"""Called after app starts."""
pass
if __name__ == "__main__":
MyApp().run()python
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static
class MyApp(App):
CSS_PATH = "app.tcss"
def compose(self) -> ComposeResult:
yield Header()
yield Static("Hello, Textual!")
yield Footer()
def on_mount(self) -> None:
"""应用启动后调用。"""
pass
if __name__ == "__main__":
MyApp().run()Widget Communication
Widget通信
Follow "Attributes down, messages up":
python
undefined遵循**“属性向下传递,消息向上发送”**原则:
python
undefinedParent sets child attributes (down)
父组件设置子组件属性(向下)
child.value = 10
child.value = 10
Child posts messages to parent (up)
子组件向父组件发送消息(向上)
class ChildWidget(Widget):
class Updated(Message):
def init(self, value: int) -> None:
super().init()
self.value = value
def update_value(self) -> None:
self.post_message(self.Updated(self.value))class ChildWidget(Widget):
class Updated(Message):
def init(self, value: int) -> None:
super().init()
self.value = value
def update_value(self) -> None:
self.post_message(self.Updated(self.value))Parent handles child messages
父组件处理子组件消息
class ParentWidget(Widget):
def on_child_widget_updated(self, message: ChildWidget.Updated) -> None:
self.log(f"Child updated: {message.value}")
undefinedclass ParentWidget(Widget):
def on_child_widget_updated(self, message: ChildWidget.Updated) -> None:
self.log(f"子组件更新:{message.value}")
undefinedTesting Pattern
测试模式
python
import pytest
from my_app import MyApp
@pytest.mark.asyncio
async def test_button_click():
app = MyApp()
async with app.run_test() as pilot:
# Simulate user interaction
await pilot.click("#submit-button")
# CRITICAL: Wait for message processing
await pilot.pause()
# Assert state changed
result = app.query_one("#status")
assert "Success" in str(result.renderable)python
import pytest
from my_app import MyApp
@pytest.mark.asyncio
async def test_button_click():
app = MyApp()
async with app.run_test() as pilot:
# 模拟用户交互
await pilot.click("#submit-button")
# 关键:等待消息处理完成
await pilot.pause()
# 断言状态变化
result = app.query_one("#status")
assert "Success" in str(result.renderable)Best Practices
最佳实践
Design Process
设计流程
- Sketch First: Draw UI layout on paper before coding
- Work Outside-In: Implement fixed elements (header/footer) first, then flexible content
- Use Docking: Fix elements with
dock: top/bottom/left/right - FR Units: Use for flexible sizing that fills available space
1fr - Container Widgets: Leverage ,
Vertical,Horizontalfor layoutsGrid
- 先草图设计:编码前在纸上绘制UI布局
- 从外到内开发:先实现固定元素(页眉/页脚),再处理灵活内容
- 使用停靠:用固定元素
dock: top/bottom/left/right - FR单位:使用实现填充可用空间的灵活尺寸
1fr - 容器Widgets:利用、
Vertical、Horizontal进行布局Grid
Code Organization
代码组织
Prefer composition over inheritance:
python
undefined优先组合而非继承:
python
undefinedGood: Compose from smaller widgets
推荐:由小Widgets组合而成
class UserCard(Widget):
def compose(self) -> ComposeResult:
with Vertical():
yield Avatar()
yield UserName()
yield UserEmail()
**Separate concerns:**
```pythonclass UserCard(Widget):
def compose(self) -> ComposeResult:
with Vertical():
yield Avatar()
yield UserName()
yield UserEmail()
**关注点分离:**
```pythonUI in widgets/
UI代码放在widgets/目录
class UserPanel(Widget):
def init(self) -> None:
super().init()
self.service = UserService() # Business logic
class UserPanel(Widget):
def init(self) -> None:
super().init()
self.service = UserService() # 业务逻辑
Business logic in business_logic/
业务逻辑放在business_logic/目录
class UserService:
async def fetch_user(self, user_id: int) -> User:
# API calls, data processing
pass
**External CSS for apps:**
```python
class MyApp(App):
CSS_PATH = "app.tcss" # Enables live reloadclass UserService:
async def fetch_user(self, user_id: int) -> User:
# API调用、数据处理
pass
**应用使用外部CSS:**
```python
class MyApp(App):
CSS_PATH = "app.tcss" # 支持实时重载Performance
性能优化
- Target 60fps for smooth terminal rendering
- Use widget for cached rendering
Static - Cache expensive operations with
@lru_cache - Use immutable objects for data structures
- Workers for async operations to avoid blocking UI
- 目标60fps以实现流畅的终端渲染
- 使用Widget进行缓存渲染
Static - 用缓存昂贵操作
@lru_cache - 数据结构使用不可变对象
- 异步操作使用Workers避免阻塞UI
Accessibility
可访问性
- Full keyboard navigation support
- Set on interactive widgets
can_focus = True - Provide meaningful key bindings
- Use semantic color variables (,
$primary)$error - Test with different terminal sizes
- 支持完整的键盘导航
- 交互式Widgets设置
can_focus = True - 提供有意义的按键绑定
- 使用语义颜色变量(、
$primary)$error - 在不同终端尺寸下测试
Common Errors & Solutions
常见错误与解决方案
1. Forgetting async/await
1. 忘记使用async/await
python
undefinedpython
undefinedWRONG
错误写法
def on_button_pressed(self):
self.mount(Widget())
def on_button_pressed(self):
self.mount(Widget())
RIGHT
正确写法
async def on_button_pressed(self):
await self.mount(Widget())
undefinedasync def on_button_pressed(self):
await self.mount(Widget())
undefined2. Missing pilot.pause() in tests
2. 测试中缺少pilot.pause()
python
undefinedpython
undefinedWRONG - race condition
错误写法 - 存在竞态条件
async def test_feature():
await pilot.click("#button")
assert app.query_one("#status").text == "Done"
async def test_feature():
await pilot.click("#button")
assert app.query_one("#status").text == "Done"
RIGHT
正确写法
async def test_feature():
await pilot.click("#button")
await pilot.pause() # Wait for processing
assert app.query_one("#status").text == "Done"
undefinedasync def test_feature():
await pilot.click("#button")
await pilot.pause() # 等待处理完成
assert app.query_one("#status").text == "Done"
undefined3. Modifying reactives in init
3. 在__init__中修改响应式属性
python
undefinedpython
undefinedWRONG - triggers watchers too early
错误写法 - 过早触发监听器
def init(self):
super().init()
self.count = 10
def init(self):
super().init()
self.count = 10
RIGHT - use set_reactive or on_mount
正确写法 - 使用set_reactive或on_mount
def init(self):
super().init()
self.set_reactive(MyWidget.count, 10)
undefineddef init(self):
super().init()
self.set_reactive(MyWidget.count, 10)
undefined4. Blocking the event loop
4. 阻塞事件循环
python
undefinedpython
undefinedWRONG
错误写法
def on_button_pressed(self):
response = requests.get("https://api.example.com") # Blocks UI!
def on_button_pressed(self):
response = requests.get("https://api.example.com") # 阻塞UI!
RIGHT - use workers
正确写法 - 使用Workers
from textual.worker import work
@work(exclusive=True)
async def on_button_pressed(self):
response = await httpx.get("https://api.example.com")
undefinedfrom textual.worker import work
@work(exclusive=True)
async def on_button_pressed(self):
response = await httpx.get("https://api.example.com")
undefinedDevelopment Tools
开发工具
Development Console
开发控制台
Terminal 1:
bash
textual consoleTerminal 2:
bash
textual run --dev my_app.pyIn code:
python
from textual import log
log("Debug message", locals())终端1:
bash
textual console终端2:
bash
textual run --dev my_app.py代码中:
python
from textual import log
log("调试消息", locals())Screenshots & Live Editing
截图与实时编辑
bash
undefinedbash
undefinedScreenshot after 5 seconds
5秒后截图
textual run --screenshot 5 my_app.py
textual run --screenshot 5 my_app.py
Dev mode with live CSS reload
开发模式,支持CSS实时重载
textual run --dev my_app.py
undefinedtextual run --dev my_app.py
undefinedProject Structure
项目结构
Medium/Large Apps:
project/
├── src/
│ ├── app.py # Main App class
│ ├── screens/
│ │ ├── main_screen.py
│ │ └── settings_screen.py
│ ├── widgets/
│ │ ├── status_bar.py
│ │ └── data_grid.py
│ └── business_logic/
│ ├── models.py
│ └── services.py
├── static/
│ └── app.tcss # External CSS
├── tests/
│ ├── test_app.py
│ └── test_widgets/
└── pyproject.toml中型/大型应用:
project/
├── src/
│ ├── app.py # 主App类
│ ├── screens/
│ │ ├── main_screen.py
│ │ └── settings_screen.py
│ ├── widgets/
│ │ ├── status_bar.py
│ │ └── data_grid.py
│ └── business_logic/
│ ├── models.py
│ └── services.py
├── static/
│ └── app.tcss # 外部CSS
├── tests/
│ ├── test_app.py
│ └── test_widgets/
└── pyproject.tomlInstructions for Assistance
协助指南
When helping users with Textual:
- Assess Context: Understand their app structure and goals
- Check Basics: Verify imports, async/await, and lifecycle methods
- Provide Examples: Show concrete, runnable code
- Explain Patterns: Describe why a pattern is recommended
- Test Guidance: Include testing code when implementing features
- Debug Support: Use console logging and visual debugging tips
- Best Practices: Suggest improvements for maintainability
Always consider:
- App complexity (simple vs multi-screen)
- State management needs (local vs global)
- Performance requirements
- Testing strategy
- Code organization and maintainability
帮助用户解决Textual相关问题时:
- 评估上下文:了解他们的应用结构和目标
- 检查基础:验证导入、async/await和生命周期方法
- 提供示例:展示具体可运行的代码
- 解释模式:说明推荐某种模式的原因
- 测试指导:实现功能时包含测试代码
- 调试支持:使用控制台日志和可视化调试技巧
- 最佳实践:提出可维护性改进建议
始终考虑:
- 应用复杂度(简单应用vs多屏幕应用)
- 状态管理需求(本地vs全局)
- 性能要求
- 测试策略
- 代码组织与可维护性
Additional Resources
额外资源
For detailed reference information:
- quick-reference.md: Concise templates, patterns, and cheat sheets
- guide.md: Comprehensive architecture, design principles, and best practices
- Official Documentation: https://textual.textualize.io
如需详细参考信息:
- quick-reference.md:简洁的模板、模式和速查表
- guide.md:全面的架构、设计原则和最佳实践
- 官方文档:https://textual.textualize.io
Quick Reference Highlights
速查表重点
Useful Built-in Widgets
实用内置Widgets
Input & Selection:
- ,
Button,Checkbox,Input,RadioButton,Select,SwitchTextArea
Display:
- ,
Label,Static,Pretty,MarkdownMarkdownViewer
Data:
- ,
DataTable,ListView,TreeDirectoryTree
Containers:
- ,
Header,Footer,Tabs,TabbedContent,Vertical,HorizontalGrid
输入与选择:
- ,
Button,Checkbox,Input,RadioButton,Select,SwitchTextArea
显示组件:
- ,
Label,Static,Pretty,MarkdownMarkdownViewer
数据组件:
- ,
DataTable,ListView,TreeDirectoryTree
容器组件:
- ,
Header,Footer,Tabs,TabbedContent,Vertical,HorizontalGrid
Key Lifecycle Methods
关键生命周期方法
python
def __init__(self) -> None:
"""Widget created - don't modify reactives here."""
super().__init__()
def compose(self) -> ComposeResult:
"""Build child widgets."""
yield ChildWidget()
def on_mount(self) -> None:
"""After mounted - safe to modify reactives."""
self.set_interval(1, self.update)
def on_unmount(self) -> None:
"""Before removal - cleanup resources."""
passpython
def __init__(self) -> None:
"""创建Widget - 不要在此修改响应式属性。"""
super().__init__()
def compose(self) -> ComposeResult:
"""构建子Widgets。"""
yield ChildWidget()
def on_mount(self) -> None:
"""挂载完成后 - 可安全修改响应式属性。"""
self.set_interval(1, self.update)
def on_unmount(self) -> None:
"""移除前 - 清理资源。"""
passCommon CSS Patterns
常见CSS模式
css
/* Docking */
#header { dock: top; height: 3; }
#sidebar { dock: left; width: 30; }
/* Flexible sizing */
#content { width: 1fr; height: 1fr; }
/* Grid layout */
#container {
layout: grid;
grid-size: 3 2;
grid-columns: 1fr 2fr 1fr;
}
/* Theme colors */
Button {
background: $primary;
color: $text;
}
Button:hover {
background: $primary-lighten-1;
}css
/* 停靠布局 */
#header { dock: top; height: 3; }
#sidebar { dock: left; width: 30; }
/* 灵活尺寸 */
#content { width: 1fr; height: 1fr; }
/* 网格布局 */
#container {
layout: grid;
grid-size: 3 2;
grid-columns: 1fr 2fr 1fr;
}
/* 主题颜色 */
Button {
background: $primary;
color: $text;
}
Button:hover {
background: $primary-lighten-1;
}Summary
总结
This skill provides expert-level guidance for building Textual applications. Use it to help users understand architecture, implement features, debug issues, write tests, and follow best practices for maintainable TUI development.
本技能为构建Textual应用提供专家级指导。用于帮助用户理解架构、实现功能、调试问题、编写测试,并遵循可维护TUI开发的最佳实践。