Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:58:08 +08:00
commit 9f0794c603
13 changed files with 4258 additions and 0 deletions

575
skill/references/layouts.md Normal file
View File

@@ -0,0 +1,575 @@
# Textual Layout Patterns
Common layout recipes for Textual applications.
## Layout Types
### Vertical (Default)
Stack widgets vertically:
```python
from textual.app import App, ComposeResult
from textual.widgets import Label
class VerticalApp(App):
def compose(self) -> ComposeResult:
yield Label("Top")
yield Label("Middle")
yield Label("Bottom")
# Or explicit CSS
CSS = """
Screen {
layout: vertical;
}
"""
```
### Horizontal
Arrange widgets side-by-side:
```python
from textual.containers import Horizontal
def compose(self) -> ComposeResult:
with Horizontal():
yield Label("Left")
yield Label("Center")
yield Label("Right")
# Or via CSS
CSS = """
Horizontal {
height: 100%;
}
Horizontal > Label {
width: 1fr; /* Equal distribution */
}
"""
```
### Grid
Create grid layouts:
```python
class GridApp(App):
CSS = """
Screen {
layout: grid;
grid-size: 3 2; /* 3 columns, 2 rows */
grid-gutter: 1;
}
.cell {
border: solid $accent;
height: 100%;
}
"""
def compose(self) -> ComposeResult:
for i in range(6):
yield Label(f"Cell {i+1}", classes="cell")
```
Advanced grid with spanning:
```python
CSS = """
Screen {
layout: grid;
grid-size: 4; /* 4 columns, auto rows */
}
#header {
column-span: 4; /* Spans all columns */
}
#sidebar {
row-span: 2; /* Spans 2 rows */
}
"""
```
### Dock Layout
Dock widgets to edges:
```python
from textual.widgets import Header, Footer
class DockedApp(App):
def compose(self) -> ComposeResult:
yield Header() # Docked to top
yield Label("Content") # Takes remaining space
yield Footer() # Docked to bottom
# Custom docking
CSS = """
#sidebar {
dock: left;
width: 30;
}
#toolbar {
dock: top;
height: 3;
}
"""
```
## Common Patterns
### Split Screen (Vertical)
Two panels side-by-side:
```python
class SplitScreen(App):
CSS = """
Screen {
layout: horizontal;
}
#left-panel {
width: 30%;
border-right: solid $accent;
}
#right-panel {
width: 70%;
}
"""
def compose(self) -> ComposeResult:
with Container(id="left-panel"):
yield Label("Sidebar")
with Container(id="right-panel"):
yield Label("Main content")
```
### Split Screen (Horizontal)
Two panels stacked:
```python
class SplitScreenHorizontal(App):
CSS = """
Screen {
layout: vertical;
}
#top-panel {
height: 50%;
border-bottom: solid $accent;
}
#bottom-panel {
height: 50%;
}
"""
def compose(self) -> ComposeResult:
with Container(id="top-panel"):
yield Label("Top content")
with Container(id="bottom-panel"):
yield Label("Bottom content")
```
### Three-Column Layout
Classic sidebar-content-sidebar:
```python
class ThreeColumn(App):
CSS = """
Screen {
layout: horizontal;
}
#left-sidebar {
width: 20;
}
#content {
width: 1fr; /* Take remaining space */
}
#right-sidebar {
width: 25;
}
"""
def compose(self) -> ComposeResult:
with Container(id="left-sidebar"):
yield Label("Menu")
with Container(id="content"):
yield Label("Main")
with Container(id="right-sidebar"):
yield Label("Info")
```
### Dashboard Grid
Grid-based dashboard:
```python
class Dashboard(App):
CSS = """
Screen {
layout: grid;
grid-size: 2 3; /* 2 columns, 3 rows */
grid-gutter: 1 2; /* vertical horizontal */
}
#header {
column-span: 2;
height: 3;
}
.metric-card {
border: solid $primary;
padding: 1;
}
"""
def compose(self) -> ComposeResult:
yield Header(id="header")
yield Static("Users: 1,234", classes="metric-card")
yield Static("Revenue: $12K", classes="metric-card")
yield Static("Growth: +15%", classes="metric-card")
yield Static("Active: 567", classes="metric-card")
```
### Centered Content
Center content horizontally and vertically:
```python
class CenteredApp(App):
CSS = """
Screen {
align: center middle;
}
#dialog {
width: 60;
height: 20;
border: thick $accent;
padding: 2;
background: $surface;
}
"""
def compose(self) -> ComposeResult:
with Container(id="dialog"):
yield Label("Centered Dialog")
yield Button("OK")
```
### Scrollable Content
Handle overflow with scrolling:
```python
from textual.containers import ScrollableContainer
class ScrollableApp(App):
CSS = """
#content {
height: 100%;
border: solid $primary;
}
"""
def compose(self) -> ComposeResult:
with ScrollableContainer(id="content"):
for i in range(100):
yield Label(f"Line {i+1}")
```
### Tabbed Interface
Tab-based navigation:
```python
from textual.widgets import TabbedContent, TabPane
class TabbedApp(App):
def compose(self) -> ComposeResult:
with TabbedContent():
with TabPane("Dashboard"):
yield Label("Dashboard content")
with TabPane("Users"):
yield Label("Users content")
with TabPane("Settings"):
yield Label("Settings content")
```
## Sizing Strategies
### Fixed Sizes
Absolute dimensions:
```css
#widget {
width: 40; /* 40 columns */
height: 20; /* 20 rows */
}
```
### Fractional Units
Proportional sizing:
```css
#sidebar {
width: 1fr; /* 1 part */
}
#content {
width: 3fr; /* 3 parts (3x sidebar) */
}
```
### Percentage
Relative to parent:
```css
#widget {
width: 50%; /* Half of parent width */
height: 100%; /* Full parent height */
}
```
### Auto Sizing
Size to content:
```css
#widget {
width: auto; /* Width matches content */
height: auto; /* Height matches content */
}
```
### Min/Max Constraints
Bounded sizing:
```css
#widget {
width: 1fr;
min-width: 30;
max-width: 80;
}
```
## Spacing and Alignment
### Padding
Space inside widget:
```css
#widget {
padding: 1; /* All sides */
padding: 1 2; /* Vertical Horizontal */
padding: 1 2 1 2; /* Top Right Bottom Left */
padding-top: 1; /* Individual sides */
}
```
### Margin
Space outside widget:
```css
#widget {
margin: 1;
margin: 0 2; /* No vertical, 2 horizontal */
margin-left: 1;
}
```
### Alignment
Position within container:
```css
Container {
align: center middle; /* Horizontal Vertical */
align: left top;
align: right bottom;
}
/* Content alignment (for containers) */
Container {
content-align: center middle;
}
```
## Responsive Layouts
### Container Queries
Adjust based on container size:
```python
class ResponsiveApp(App):
CSS = """
Screen {
layout: horizontal;
}
/* Default mobile layout */
#content {
layout: vertical;
}
/* Desktop layout when width > 80 */
Screen:width-gt-80 #content {
layout: horizontal;
}
"""
```
### Conditional Layouts
Switch layouts based on screen size:
```python
def compose(self) -> ComposeResult:
if self.size.width > 100:
# Wide layout
with Horizontal():
yield self.make_sidebar()
yield self.make_content()
else:
# Narrow layout
with Vertical():
yield self.make_content()
```
## Advanced Patterns
### Modal Overlay
Centered modal dialog:
```python
from textual.screen import ModalScreen
from textual.containers import Container
class Modal(ModalScreen[bool]):
CSS = """
Modal {
align: center middle;
}
#dialog {
width: 50;
height: 15;
border: thick $accent;
background: $surface;
padding: 1;
}
"""
def compose(self) -> ComposeResult:
with Container(id="dialog"):
yield Label("Are you sure?")
with Horizontal():
yield Button("Yes", variant="primary")
yield Button("No", variant="error")
```
### Sidebar Toggle
Collapsible sidebar:
```python
class SidebarApp(App):
show_sidebar = reactive(True)
CSS = """
#sidebar {
width: 30;
transition: width 200ms;
}
#sidebar.hidden {
width: 0;
display: none;
}
"""
def watch_show_sidebar(self, show: bool) -> None:
sidebar = self.query_one("#sidebar")
sidebar.set_class(not show, "hidden")
```
### Masonry Layout
Staggered grid:
```python
class MasonryLayout(App):
CSS = """
Screen {
layout: grid;
grid-size: 3;
grid-gutter: 1;
}
.card {
height: auto;
border: solid $primary;
padding: 1;
}
.card.tall {
row-span: 2;
}
"""
def compose(self) -> ComposeResult:
yield Static("Short card", classes="card")
yield Static("Tall card\n\n\n", classes="card tall")
yield Static("Short", classes="card")
```
### Split Resizable
Adjustable split panels:
```python
class ResizableSplit(App):
left_width = reactive(30)
CSS = """
#left {
width: var(--left-width);
}
#right {
width: 1fr;
}
#divider {
width: 1;
background: $accent;
}
"""
def watch_left_width(self, width: int) -> None:
self.set_var("left-width", width)
```
## Layout Debugging
Use borders to visualize layout:
```css
* {
border: solid red; /* Temporary debugging */
}
Container {
border: solid blue;
}
Widget {
border: solid green;
}
```
Use Textual devtools:
```bash
textual run --dev app.py
```
Add debug info to widgets:
```python
def compose(self) -> ComposeResult:
yield Label(f"Size: {self.size}")
yield Label(f"Region: {self.region}")
```

View File

@@ -0,0 +1,284 @@
# Textual Official Guide Index
High-level index of every guide from the official Textual documentation. Use `web_fetch` to retrieve full content on-demand when needed.
## Getting Started
### Installation
**URL:** https://textual.textualize.io/getting_started/
**Topics:** Installing Textual, requirements, first app
**When to fetch:** User asks about installation, setup, or getting started
### Tutorial
**URL:** https://textual.textualize.io/tutorial/
**Topics:** Building a stopwatch app, complete walkthrough, reactive attributes, widgets
**When to fetch:** User wants step-by-step tutorial or building their first app
## Core Concepts
### App Basics
**URL:** https://textual.textualize.io/guide/app/
**Topics:** Creating apps, running apps, compose method, mounting widgets, app lifecycle, application mode, inline mode, suspending apps
**When to fetch:** Questions about app structure, lifecycle, or basic app operations
### Widgets
**URL:** https://textual.textualize.io/guide/widgets/
**Topics:** Creating custom widgets, widget communication, compound widgets, render method, line API, renderables, uni-directional data flow, widget design patterns
**When to fetch:** Creating custom widgets, widget architecture, or advanced widget patterns
### Layout
**URL:** https://textual.textualize.io/guide/layout/
**Topics:** Vertical layout, horizontal layout, grid layout, dock layout, layers, FR units, container layouts, grid-size, grid-columns, grid-rows, row-span, column-span
**When to fetch:** Layout questions, arranging widgets, grid systems, positioning
### CSS & Styling
**URL:** https://textual.textualize.io/guide/CSS/
**Topics:** Textual CSS basics, selectors (type, ID, class, universal), pseudo-classes, CSS_PATH, live editing, combinator selectors
**When to fetch:** CSS syntax, selectors, styling fundamentals
### Styles
**URL:** https://textual.textualize.io/guide/styles/
**Topics:** Style properties, colors (hex, RGB, HSL), units (%, fr, vw, vh, w, h), box model, box-sizing, dimensions, spacing
**When to fetch:** Specific style properties, units, colors, dimensions
### Design System / Themes
**URL:** https://textual.textualize.io/guide/design/
**Topics:** Theme system, built-in themes, creating custom themes, color variables, semantic colors, $text variables, theme switching, design tokens
**When to fetch:** Theming, color systems, design tokens, theme customization
## Interaction & Events
### Input
**URL:** https://textual.textualize.io/guide/input/
**Topics:** Keyboard input, mouse input, focus, key events, mouse events, input handling
**When to fetch:** Handling keyboard/mouse input, focus management, input events
### Events
**URL:** https://textual.textualize.io/guide/events/
**Topics:** Event system, message handlers, event bubbling, preventing events, custom events, event lifecycle
**When to fetch:** Event handling, custom events, event propagation
### Actions
**URL:** https://textual.textualize.io/guide/actions/
**Topics:** Action system, key bindings, BINDINGS, action_* methods, built-in actions, custom actions
**When to fetch:** Keyboard shortcuts, actions, key bindings
### Reactivity
**URL:** https://textual.textualize.io/guide/reactivity/
**Topics:** Reactive attributes, watch methods, compute methods, reactive decorators, smart refresh, recompose, data binding
**When to fetch:** Reactive programming, automatic updates, computed values, watchers
## Advanced Features
### Screens
**URL:** https://textual.textualize.io/guide/screens/
**Topics:** Screen stack, push_screen, pop_screen, ModalScreen, screen navigation, installed screens, SCREENS, screen opacity
**When to fetch:** Multi-screen apps, navigation, modal dialogs, screen management
### Query
**URL:** https://textual.textualize.io/guide/queries/
**Topics:** Querying widgets, query_one, query, set methods, DOM traversal, selectors in queries
**When to fetch:** Finding widgets, DOM navigation, bulk operations
### Workers
**URL:** https://textual.textualize.io/guide/workers/
**Topics:** Background tasks, @work decorator, run_worker, thread workers, async workers, worker lifecycle, cancellation
**When to fetch:** Background processing, async operations, threading, long-running tasks
### Animation
**URL:** https://textual.textualize.io/guide/animation/
**Topics:** Animating styles, animate method, easing functions, duration, transitions, animation callbacks
**When to fetch:** Animations, transitions, easing, style animations
### Command Palette
**URL:** https://textual.textualize.io/guide/command_palette/
**Topics:** Built-in command palette, Provider class, fuzzy matching, custom commands, command discovery
**When to fetch:** Command palette, custom commands, keyboard-driven interfaces
## Content & Display
### Content / Markup
**URL:** https://textual.textualize.io/guide/content/
**Topics:** Content markup, Rich renderables, styling text, markup tags, links, clickable actions, content objects
**When to fetch:** Text formatting, markup syntax, Rich renderables, styled text
### Rich Content
**URL:** https://textual.textualize.io/guide/rich/
**Topics:** Using Rich library, Rich renderables, tables, syntax highlighting, panels, progress bars
**When to fetch:** Rich library integration, advanced text formatting, Rich features
## Development & Debugging
### Devtools
**URL:** https://textual.textualize.io/guide/devtools/
**Topics:** textual command, console, run command, live CSS editing, --dev mode, debugging, logging
**When to fetch:** Development workflow, debugging, console logging, dev tools
### Testing
**URL:** https://textual.textualize.io/guide/testing/
**Topics:** Testing apps, Pilot API, run_test, simulating input, snapshots, unit testing
**When to fetch:** Testing, unit tests, test automation, Pilot
### Performance
**URL:** https://textual.textualize.io/guide/performance/
**Topics:** Performance optimization, profiling, rendering performance, widget efficiency, best practices
**When to fetch:** Performance issues, optimization, profiling
## Reference Documentation
### Widgets Reference
**URL:** https://textual.textualize.io/widgets/
**Topics:** Complete widget reference, all built-in widgets with examples
**When to fetch:** Looking for specific widget documentation, widget API details
### Styles Reference
**URL:** https://textual.textualize.io/styles/
**Topics:** Complete CSS properties reference, all style properties
**When to fetch:** Specific style property details, CSS reference
### API Reference
**URL:** https://textual.textualize.io/api/
**Topics:** Complete Python API reference
**When to fetch:** API details, method signatures, class documentation
## Specialized Topics
### Scrolling
**URL:** https://textual.textualize.io/guide/scrolling/
**Topics:** Scroll views, ScrollableContainer, scrolling behavior, scroll_visible, programmatic scrolling
**When to fetch:** Scrolling issues, scroll containers, programmatic scrolling
### Tooltips
**URL:** https://textual.textualize.io/guide/tooltips/
**Topics:** Adding tooltips, tooltip property, tooltip customization
**When to fetch:** Tooltips, hover help text
### Notifications
**URL:** https://textual.textualize.io/guide/notifications/
**Topics:** Toast notifications, notify method, notification severity, notification styling
**When to fetch:** Notifications, alerts, toasts
### Input Validation
**URL:** https://textual.textualize.io/guide/input_validation/
**Topics:** Validating input, Validator class, built-in validators, custom validators
**When to fetch:** Form validation, input validation, validators
### Timers
**URL:** https://textual.textualize.io/guide/timers/
**Topics:** Scheduling tasks, set_timer, set_interval, timer callbacks, timer management
**When to fetch:** Scheduled tasks, periodic updates, timers
### Paths
**URL:** https://textual.textualize.io/guide/paths/
**Topics:** File paths, resource paths, CSS_PATH, path resolution
**When to fetch:** File loading, resource paths, path management
## Additional Topics
### FAQ
**URL:** https://textual.textualize.io/FAQ/
**Topics:** Common questions, troubleshooting, best practices
**When to fetch:** Common issues, general questions, troubleshooting
### Why Textual?
**URL:** https://textual.textualize.io/guide/why/
**Topics:** Benefits of Textual, use cases, comparison with alternatives
**When to fetch:** Understanding Textual benefits, when to use Textual
## Usage Guidelines
### When to Fetch Guides
1. **Don't fetch unless needed**: The skill already covers fundamentals. Only fetch when:
- User asks about a specific topic not covered in skill
- Need detailed API information
- Complex examples required
- Latest updates needed (docs may be newer than skill)
2. **Fetch specific sections**: Use targeted URLs for relevant topics
3. **Combine with skill knowledge**: Use fetched content to supplement, not replace, skill knowledge
### Example Fetch Patterns
```python
# User asks about command palette
web_fetch("https://textual.textualize.io/guide/command_palette/")
# User needs animation details
web_fetch("https://textual.textualize.io/guide/animation/")
# User wants testing info
web_fetch("https://textual.textualize.io/guide/testing/")
# Need widget reference
web_fetch("https://textual.textualize.io/widgets/data_table/")
```
## Quick Reference by Topic
### Need information about...
**App Structure** → App Basics guide
**Layout & Positioning** → Layout guide
**Styling & CSS** → CSS guide, Styles guide, Design guide
**User Input** → Input guide, Events guide, Actions guide
**Custom Widgets** → Widgets guide
**Navigation** → Screens guide
**Async Tasks** → Workers guide
**Animations** → Animation guide
**Commands** → Command Palette guide
**Testing** → Testing guide
**Development** → Devtools guide
**Rich Integration** → Rich Content guide
**Form Validation** → Input Validation guide
**Auto-Updates** → Reactivity guide
## Widget-Specific Documentation
All built-in widgets have dedicated documentation at:
`https://textual.textualize.io/widgets/{widget_name}/`
Common widget docs:
- **Button**: https://textual.textualize.io/widgets/button/
- **Input**: https://textual.textualize.io/widgets/input/
- **DataTable**: https://textual.textualize.io/widgets/data_table/
- **Tree**: https://textual.textualize.io/widgets/tree/
- **Select**: https://textual.textualize.io/widgets/select/
- **TextArea**: https://textual.textualize.io/widgets/text_area/
- **ListView**: https://textual.textualize.io/widgets/list_view/
- **ProgressBar**: https://textual.textualize.io/widgets/progress_bar/
- **Markdown**: https://textual.textualize.io/widgets/markdown/
- **MarkdownViewer**: https://textual.textualize.io/widgets/markdown_viewer/
- **DirectoryTree**: https://textual.textualize.io/widgets/directory_tree/
- **Header**: https://textual.textualize.io/widgets/header/
- **Footer**: https://textual.textualize.io/widgets/footer/
- **Label**: https://textual.textualize.io/widgets/label/
- **Static**: https://textual.textualize.io/widgets/static/
- **Log**: https://textual.textualize.io/widgets/log/
- **RichLog**: https://textual.textualize.io/widgets/rich_log/
- **Sparkline**: https://textual.textualize.io/widgets/sparkline/
- **Switch**: https://textual.textualize.io/widgets/switch/
- **Checkbox**: https://textual.textualize.io/widgets/checkbox/
- **RadioButton**: https://textual.textualize.io/widgets/radio_button/
- **RadioSet**: https://textual.textualize.io/widgets/radio_set/
- **TabbedContent**: https://textual.textualize.io/widgets/tabbed_content/
- **ContentSwitcher**: https://textual.textualize.io/widgets/content_switcher/
- **LoadingIndicator**: https://textual.textualize.io/widgets/loading_indicator/
## Container Widget Documentation
- **Container**: https://textual.textualize.io/widgets/container/
- **Horizontal**: https://textual.textualize.io/widgets/horizontal/
- **Vertical**: https://textual.textualize.io/widgets/vertical/
- **Grid**: https://textual.textualize.io/widgets/grid/
- **ScrollableContainer**: https://textual.textualize.io/widgets/scrollable_container/
- **VerticalScroll**: https://textual.textualize.io/widgets/vertical_scroll/
- **HorizontalScroll**: https://textual.textualize.io/widgets/horizontal_scroll/
## Notes
- All URLs follow pattern: `https://textual.textualize.io/{section}/{topic}/`
- Official docs are actively maintained and may have updates not in this skill
- Use web_fetch with specific URLs when detailed or latest information needed
- Combine official docs with skill knowledge for best results

700
skill/references/styling.md Normal file
View File

@@ -0,0 +1,700 @@
# Textual CSS Styling Guide
Complete guide to styling Textual applications with TCSS (Textual CSS).
## CSS Basics
### Inline Styles
Define styles directly in widget class:
```python
class MyWidget(Widget):
DEFAULT_CSS = """
MyWidget {
background: $primary;
color: $text;
border: solid $accent;
}
"""
```
### External Stylesheets
Load from file:
```python
class MyApp(App):
CSS_PATH = "app.tcss" # Load from app.tcss file
```
### Multiple Stylesheets
Load multiple files:
```python
class MyApp(App):
CSS_PATH = ["base.tcss", "theme.tcss", "overrides.tcss"]
```
## Selectors
### Type Selectors
Target widget types:
```css
Button {
background: blue;
}
Label {
color: white;
}
```
### ID Selectors
Target specific widgets:
```css
#submit-button {
background: green;
}
#error-message {
color: red;
}
```
### Class Selectors
Target classes:
```css
.highlight {
background: yellow;
color: black;
}
.card {
border: solid white;
padding: 1;
}
```
### Pseudo-classes
Target widget states:
```css
Button:hover {
background: lighten($primary, 20%);
}
Button:focus {
border: thick $accent;
}
Input:focus {
border: solid $success;
}
/* Disabled state */
Button:disabled {
opacity: 50%;
}
```
### Descendant Selectors
Target nested widgets:
```css
/* Any Label inside a Container */
Container Label {
color: gray;
}
/* Direct children only */
Container > Label {
color: white;
}
/* Specific nesting */
#sidebar .menu-item {
padding: 1;
}
```
### Multiple Selectors
Apply same style to multiple targets:
```css
Button, Input, Select {
border: solid $accent;
}
.error, .warning {
font-weight: bold;
}
```
## Colors
### Semantic Colors
Use theme colors:
```css
Widget {
background: $background;
color: $text;
border: solid $primary;
}
/* Available semantic colors */
$primary /* Primary theme color */
$secondary /* Secondary theme color */
$accent /* Accent color */
$background /* Background color */
$surface /* Surface color */
$panel /* Panel color */
$text /* Primary text color */
$text-muted /* Muted text */
$text-disabled /* Disabled text */
$success /* Success state */
$warning /* Warning state */
$error /* Error state */
$boost /* Highlight color */
```
### Color Formats
Define custom colors:
```css
Widget {
background: #1e3a8a; /* Hex */
color: rgb(255, 255, 255); /* RGB */
border-color: rgba(255, 0, 0, 0.5); /* RGBA with alpha */
}
/* Named colors */
Widget {
background: transparent;
color: black;
border-color: white;
}
```
### Color Functions
Manipulate colors:
```css
Widget {
background: darken($primary, 20%);
color: lighten($text, 10%);
border-color: fade($accent, 50%);
}
```
## Typography
### Text Style
```css
Label {
text-style: bold; /* bold, italic, underline */
text-style: bold italic; /* Multiple styles */
text-style: reverse; /* Reverse colors */
text-style: strike; /* Strikethrough */
}
```
### Text Alignment
```css
Label {
text-align: left; /* left, center, right, justify */
}
```
### Text Opacity
```css
Label {
text-opacity: 70%; /* Semi-transparent text */
}
```
## Borders
### Border Styles
```css
Widget {
border: solid $accent; /* Solid border */
border: dashed blue; /* Dashed */
border: heavy green; /* Heavy */
border: double white; /* Double */
border: thick $primary; /* Thick */
border: none; /* No border */
}
```
### Border Sides
```css
Widget {
border-top: solid red;
border-right: dashed blue;
border-bottom: thick green;
border-left: double white;
}
```
### Border Title
```css
Widget {
border: solid $accent;
border-title-align: center; /* left, center, right */
}
```
## Dimensions
### Width
```css
Widget {
width: 40; /* Fixed columns */
width: 50%; /* Percentage of parent */
width: 1fr; /* Fractional unit */
width: auto; /* Size to content */
}
```
### Height
```css
Widget {
height: 20; /* Fixed rows */
height: 100%; /* Full parent height */
height: auto; /* Size to content */
}
```
### Min/Max Constraints
```css
Widget {
min-width: 20;
max-width: 80;
min-height: 10;
max-height: 50;
}
```
## Spacing
### Padding
Space inside widget:
```css
Widget {
padding: 1; /* All sides */
padding: 1 2; /* Vertical Horizontal */
padding: 1 2 3 4; /* Top Right Bottom Left */
}
/* Individual sides */
Widget {
padding-top: 1;
padding-right: 2;
padding-bottom: 1;
padding-left: 2;
}
```
### Margin
Space outside widget:
```css
Widget {
margin: 1;
margin: 0 2;
margin: 1 2 1 2;
}
/* Individual sides */
Widget {
margin-top: 1;
margin-right: 2;
}
```
## Layout Properties
### Display
Control visibility:
```css
Widget {
display: block; /* Visible */
display: none; /* Hidden */
}
```
### Visibility
Alternative to display:
```css
Widget {
visibility: visible;
visibility: hidden; /* Hidden but takes space */
}
```
### Opacity
Transparency:
```css
Widget {
opacity: 100%; /* Fully opaque */
opacity: 50%; /* Semi-transparent */
opacity: 0%; /* Fully transparent */
}
```
### Layout Type
```css
Container {
layout: vertical; /* Stack vertically */
layout: horizontal; /* Stack horizontally */
layout: grid; /* Grid layout */
}
```
### Grid Properties
```css
Container {
layout: grid;
grid-size: 3 2; /* 3 columns, 2 rows */
grid-gutter: 1 2; /* Vertical Horizontal gaps */
grid-rows: 10 auto 1fr; /* Row sizes */
grid-columns: 1fr 2fr; /* Column sizes */
}
/* Grid item spanning */
Widget {
column-span: 2; /* Span 2 columns */
row-span: 3; /* Span 3 rows */
}
```
### Alignment
```css
Container {
align: center middle; /* Horizontal Vertical */
align-horizontal: left; /* left, center, right */
align-vertical: top; /* top, middle, bottom */
}
/* Content alignment */
Container {
content-align: center middle;
content-align-horizontal: right;
content-align-vertical: bottom;
}
```
### Scrollbars
```css
Widget {
overflow: auto; /* Show scrollbars when needed */
overflow: scroll; /* Always show scrollbars */
overflow: hidden; /* No scrollbars */
}
/* Individual axes */
Widget {
overflow-x: auto;
overflow-y: scroll;
}
/* Scrollbar styling */
Widget {
scrollbar-background: $panel;
scrollbar-color: $primary;
scrollbar-color-hover: $accent;
scrollbar-color-active: $boost;
}
```
## Effects
### Transitions
Animate property changes:
```css
Button {
background: blue;
transition: background 300ms;
}
Button:hover {
background: lightblue; /* Animates over 300ms */
}
/* Multiple properties */
Widget {
transition: background 200ms, border 150ms;
}
```
### Offset
Position adjustment:
```css
Widget {
offset: 1 2; /* X Y offset */
offset-x: 1;
offset-y: 2;
}
```
### Layer
Z-index equivalent:
```css
Widget {
layer: above; /* Higher layer */
layer: below; /* Lower layer */
}
```
## Docking
Pin widgets to edges:
```css
#header {
dock: top;
height: 3;
}
#sidebar {
dock: left;
width: 30;
}
#footer {
dock: bottom;
height: 3;
}
```
## Theme Variables
Define reusable values:
```css
/* Define variables */
Screen {
--card-bg: #1e3a8a;
--card-border: white;
--card-padding: 1 2;
}
/* Use variables */
.card {
background: var(--card-bg);
border: solid var(--card-border);
padding: var(--card-padding);
}
```
## Complete Theme Example
```css
/* app.tcss */
/* Theme colors */
Screen {
background: #0f172a;
color: #e2e8f0;
}
/* Headers */
Header {
background: #1e293b;
color: #60a5fa;
dock: top;
height: 3;
}
Footer {
background: #1e293b;
color: #94a3b8;
dock: bottom;
height: 1;
}
/* Buttons */
Button {
background: #3b82f6;
color: white;
border: none;
margin: 0 1;
padding: 0 2;
min-width: 16;
transition: background 200ms;
}
Button:hover {
background: #60a5fa;
}
Button:focus {
border: solid #93c5fd;
}
Button.-primary {
background: #10b981;
}
Button.-primary:hover {
background: #34d399;
}
Button.-error {
background: #ef4444;
}
Button.-error:hover {
background: #f87171;
}
/* Inputs */
Input {
border: solid #475569;
background: #1e293b;
color: #e2e8f0;
padding: 0 1;
}
Input:focus {
border: solid #3b82f6;
}
/* Containers */
.card {
background: #1e293b;
border: solid #334155;
padding: 1 2;
margin: 1;
}
.card > .title {
text-style: bold;
color: #60a5fa;
margin-bottom: 1;
}
/* Data tables */
DataTable {
background: #1e293b;
}
DataTable > .datatable--header {
background: #334155;
color: #60a5fa;
text-style: bold;
}
DataTable > .datatable--cursor {
background: #3b82f6;
}
/* Scrollbars */
*::-webkit-scrollbar {
scrollbar-background: #1e293b;
scrollbar-color: #475569;
}
*::-webkit-scrollbar:hover {
scrollbar-color: #64748b;
}
```
## Dark/Light Themes
Support theme switching:
```python
class MyApp(App):
ENABLE_DARK_MODE = True
CSS = """
/* Dark theme (default) */
Screen {
background: #0f172a;
color: #e2e8f0;
}
/* Light theme */
Screen.light {
background: #f8fafc;
color: #1e293b;
}
"""
def action_toggle_theme(self) -> None:
self.dark = not self.dark
```
## Responsive Styles
Conditional styles based on size:
```css
/* Default (small screens) */
#sidebar {
width: 100%;
}
/* Medium screens */
Screen:width-gt-80 #sidebar {
width: 30;
}
/* Large screens */
Screen:width-gt-120 #sidebar {
width: 40;
}
```
## Best Practices
1. **Use semantic colors** - Prefer `$primary` over hardcoded values
2. **Organize CSS** - Group related styles together
3. **Use classes** - Reusable styles via classes, not IDs
4. **Minimize specificity** - Avoid overly specific selectors
5. **Use transitions** - Smooth state changes
6. **Test both themes** - Ensure dark/light compatibility
7. **Keep CSS DRY** - Use variables for repeated values
8. **Document custom variables** - Comment non-obvious choices
## Debugging Styles
View computed styles:
```python
def on_mount(self) -> None:
widget = self.query_one("#my-widget")
self.log(widget.styles) # Log all computed styles
```
Use Textual devtools:
```bash
textual run --dev app.py
# Press F1 to view CSS inspector
```
Temporary debugging borders:
```css
* {
border: solid red; /* See all widget boundaries */
}
```

533
skill/references/widgets.md Normal file
View File

@@ -0,0 +1,533 @@
# Textual Widget Gallery
Comprehensive examples of all built-in Textual widgets.
## Basic Widgets
### Label
Display static or dynamic text:
```python
from textual.widgets import Label
# Simple label
yield Label("Hello World")
# With styling
yield Label("Important!", classes="highlight")
# With markup
yield Label("[bold]Bold[/] and [italic]italic[/]")
# Dynamic label with reactive
class DynamicLabel(Widget):
message = reactive("Initial")
def compose(self) -> ComposeResult:
yield Label(self.message)
```
### Static
Display Rich renderables:
```python
from textual.widgets import Static
from rich.table import Table
table = Table()
table.add_column("Name")
table.add_column("Value")
table.add_row("Alpha", "100")
yield Static(table)
```
### Button
Interactive buttons with variants:
```python
from textual.widgets import Button
# Standard button
yield Button("Click me", id="action")
# Button variants
yield Button("Primary", variant="primary")
yield Button("Success", variant="success")
yield Button("Warning", variant="warning")
yield Button("Error", variant="error")
# Disabled button
yield Button("Disabled", disabled=True)
# Handle click
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "action":
self.action_perform()
```
## Input Widgets
### Input
Single-line text input:
```python
from textual.widgets import Input
# Basic input
yield Input(placeholder="Enter text...", id="name")
# Password input
yield Input(placeholder="Password", password=True, id="pass")
# Validated input
yield Input(
placeholder="Email",
validators=[Email()], # Built-in validators
id="email"
)
# Handle submission
def on_input_submitted(self, event: Input.Submitted) -> None:
value = event.value
self.log(f"Submitted: {value}")
# Handle changes
def on_input_changed(self, event: Input.Changed) -> None:
self.validate_live(event.value)
```
### TextArea
Multi-line text editor:
```python
from textual.widgets import TextArea
# Basic text area
yield TextArea(id="editor")
# With initial content
yield TextArea(
text="Initial content\nLine 2",
language="python", # Syntax highlighting
theme="monokai",
id="code"
)
# Handle changes
def on_text_area_changed(self, event: TextArea.Changed) -> None:
content = event.text_area.text
```
### Select
Dropdown selection:
```python
from textual.widgets import Select
# Basic select
options = [
("Option A", "a"),
("Option B", "b"),
("Option C", "c"),
]
yield Select(options=options, prompt="Choose...", id="choice")
# Handle selection
def on_select_changed(self, event: Select.Changed) -> None:
value = event.value # "a", "b", or "c"
self.log(f"Selected: {value}")
```
### Checkbox
Boolean input:
```python
from textual.widgets import Checkbox
yield Checkbox("Enable feature", id="feature")
# Handle changes
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
is_checked = event.value
self.toggle_feature(is_checked)
```
### RadioButton and RadioSet
Mutually exclusive options:
```python
from textual.widgets import RadioButton, RadioSet
with RadioSet(id="size"):
yield RadioButton("Small")
yield RadioButton("Medium", value=True) # Default
yield RadioButton("Large")
# Handle selection
def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
selected = event.pressed.label
self.log(f"Size: {selected}")
```
### Switch
Toggle switch:
```python
from textual.widgets import Switch
yield Switch(value=True, id="notifications")
# Handle toggle
def on_switch_changed(self, event: Switch.Changed) -> None:
is_on = event.value
self.toggle_notifications(is_on)
```
## Data Display Widgets
### DataTable
Tabular data with selection and sorting:
```python
from textual.widgets import DataTable
table = DataTable(id="users")
# Add columns
table.add_columns("Name", "Age", "City")
# Add rows (returns row key)
row_key = table.add_row("Alice", 30, "NYC")
table.add_row("Bob", 25, "LA")
# Cursor control
table.cursor_type = "row" # or "cell", "column", "none"
# Handle selection
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
row_key = event.row_key
row_data = event.row
self.log(f"Selected: {row_data}")
# Update cells
table.update_cell(row_key, "Age", 31)
# Remove rows
table.remove_row(row_key)
# Sort
table.sort("Age", reverse=True)
```
### Tree
Hierarchical data:
```python
from textual.widgets import Tree
tree = Tree("Root", id="file-tree")
# Add nodes
root = tree.root
folder = root.add("Folder", expand=True)
folder.add_leaf("file1.txt")
folder.add_leaf("file2.txt")
subfolder = folder.add("Subfolder")
subfolder.add_leaf("nested.txt")
# Handle selection
def on_tree_node_selected(self, event: Tree.NodeSelected) -> None:
node = event.node
self.log(f"Selected: {node.label}")
# Expand/collapse
def on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None:
# Load children dynamically
node = event.node
self.load_children(node)
```
### ListView
List with selection:
```python
from textual.widgets import ListView, ListItem, Label
list_view = ListView(id="menu")
# Add items
list_view.append(ListItem(Label("Item 1")))
list_view.append(ListItem(Label("Item 2")))
list_view.append(ListItem(Label("Item 3")))
# Handle selection
def on_list_view_selected(self, event: ListView.Selected) -> None:
item = event.item
self.log(f"Selected: {item}")
```
### Log / RichLog
Scrollable log output:
```python
from textual.widgets import Log, RichLog
# Simple log
log = Log(id="output", auto_scroll=True)
log.write_line("Log entry")
log.write_lines(["Line 1", "Line 2"])
# Rich log with markup
rich_log = RichLog(id="rich-output", highlight=True)
rich_log.write("[bold green]Success![/]")
rich_log.write("[red]Error occurred[/]")
# Clear log
log.clear()
```
### ProgressBar
Progress indicator:
```python
from textual.widgets import ProgressBar
# Determinate progress
progress = ProgressBar(total=100, id="progress")
progress.advance(25) # 25%
progress.update(progress=50) # 50%
# Indeterminate progress
progress = ProgressBar(total=None) # Animated spinner
```
### Sparkline
Inline data visualization:
```python
from textual.widgets import Sparkline
data = [1, 2, 3, 5, 8, 13, 21]
yield Sparkline(data, id="chart")
# Update data
sparkline = self.query_one(Sparkline)
sparkline.data = new_data
```
## Navigation Widgets
### Header / Footer
Standard app chrome:
```python
from textual.widgets import Header, Footer
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
# ... content ...
yield Footer()
```
### TabbedContent / TabPane
Tabbed interface:
```python
from textual.widgets import TabbedContent, TabPane
with TabbedContent(id="tabs"):
with TabPane("Tab 1", id="tab1"):
yield Label("Content 1")
with TabPane("Tab 2", id="tab2"):
yield Label("Content 2")
with TabPane("Tab 3", id="tab3"):
yield Label("Content 3")
# Handle tab changes
def on_tabbed_content_tab_activated(self, event: TabbedContent.TabActivated) -> None:
tab_id = event.pane.id
self.log(f"Switched to {tab_id}")
```
### ContentSwitcher
Programmatically switch content:
```python
from textual.widgets import ContentSwitcher
with ContentSwitcher(initial="view1", id="switcher"):
yield Label("View 1", id="view1")
yield Label("View 2", id="view2")
yield Label("View 3", id="view3")
# Switch views
def switch_view(self, view_id: str) -> None:
switcher = self.query_one(ContentSwitcher)
switcher.current = view_id
```
### OptionList
Selectable list of options:
```python
from textual.widgets import OptionList
from textual.widgets.option_list import Option
option_list = OptionList(
Option("Option 1", id="opt1"),
Option("Option 2", id="opt2"),
Option("Option 3", id="opt3"),
)
# Handle selection
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
option_id = event.option.id
self.log(f"Selected: {option_id}")
```
## Loading Widgets
### LoadingIndicator
Spinning loader:
```python
from textual.widgets import LoadingIndicator
# Show while loading
with LoadingIndicator():
# Content loading...
pass
# Or standalone
yield LoadingIndicator(id="loader")
```
### Placeholder
Development placeholder:
```python
from textual.widgets import Placeholder
# Quick placeholder during development
yield Placeholder("Chart goes here")
yield Placeholder(label="[b]User Profile[/]", variant="text")
```
## Special Widgets
### Markdown
Render markdown:
```python
from textual.widgets import Markdown
markdown_content = """
# Title
This is **bold** and *italic*.
- Item 1
- Item 2
```python
code block
```
"""
yield Markdown(markdown_content, id="docs")
```
### DirectoryTree
File system browser:
```python
from textual.widgets import DirectoryTree
tree = DirectoryTree("/home/user", id="files")
# Handle file selection
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected) -> None:
file_path = event.path
self.open_file(file_path)
```
## Custom Widget Example
Create composite widgets:
```python
from textual.widget import Widget
from textual.containers import Horizontal, Vertical
class UserCard(Widget):
"""Display user information."""
def __init__(self, name: str, email: str, role: str) -> None:
super().__init__()
self.name = name
self.email = email
self.role = role
def compose(self) -> ComposeResult:
with Vertical(classes="card"):
yield Label(self.name, classes="name")
yield Label(self.email, classes="email")
with Horizontal():
yield Label(f"Role: {self.role}", classes="role")
yield Button("Edit", variant="primary")
DEFAULT_CSS = """
UserCard {
border: solid $primary;
padding: 1;
margin: 1;
}
UserCard .name {
text-style: bold;
color: $text;
}
UserCard .email {
color: $text-muted;
}
"""
# Use the widget
yield UserCard("Alice Smith", "alice@example.com", "Admin")
```
## Widget Composition Patterns
### Form Layout
```python
def compose(self) -> ComposeResult:
with Container(id="form"):
yield Label("Registration Form")
yield Input(placeholder="Name", id="name")
yield Input(placeholder="Email", id="email")
yield Input(placeholder="Password", password=True, id="pass")
with Horizontal():
yield Button("Submit", variant="primary")
yield Button("Cancel")
```
### Dashboard Layout
```python
def compose(self) -> ComposeResult:
yield Header()
with Container(id="dashboard"):
with Horizontal(classes="stats"):
yield Static("[b]Users:[/] 1,234", classes="stat")
yield Static("[b]Active:[/] 567", classes="stat")
yield Static("[b]Revenue:[/] $12K", classes="stat")
with Horizontal(classes="content"):
with Vertical(id="sidebar"):
yield Label("Menu")
yield Button("Dashboard")
yield Button("Users")
yield Button("Settings")
with Container(id="main"):
yield DataTable()
yield Footer()
```