castella-core
Build desktop, web, or terminal UIs with Castella. Create widgets, components, layouts, manage reactive state, handle events, and use the theme system.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install i2y-castella-castella-core
Repository
Skill path: skills/castella-core
Build desktop, web, or terminal UIs with Castella. Create widgets, components, layouts, manage reactive state, handle events, and use the theme system.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: i2y.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install castella-core into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/i2y/castella before adding castella-core to shared team environments
- Use castella-core for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: castella-core
description: Build desktop, web, or terminal UIs with Castella. Create widgets, components, layouts, manage reactive state, handle events, and use the theme system.
---
# Castella Core UI Development
Castella is a pure Python cross-platform UI framework for desktop (GLFW/SDL2), web (PyScript/Pyodide), and terminal (prompt-toolkit) applications. Write once, run everywhere with GPU-accelerated rendering via Skia.
**When to use**: "create a Castella app", "build a Castella UI", "Castella component", "add a button/input/text", "use reactive state", "layout with Row/Column", "change the theme", "handle click events", "preserve scroll position", "animate a widget"
## Quick Start
Create a minimal Castella app:
```python
from castella import App, Text
from castella.frame import Frame
App(Frame("Hello", 800, 600), Text("Hello, Castella!")).run()
```
Install and run:
```bash
uv sync --extra glfw # Desktop with GLFW
uv run python app.py
```
## Core Concepts
### App and Frame
- `Frame(title, width, height)` - Window/container for the UI
- `App(frame, widget)` - Application entry point with `.run()`
- Frame auto-selects platform: GLFW (desktop), Web, or Terminal
```python
from castella import App
from castella.frame import Frame
frame = Frame("My App", 800, 600)
app = App(frame, my_widget)
app.run()
```
### Widgets
Base building blocks for UI elements:
| Widget | Description | Key Methods |
|--------|-------------|-------------|
| `Text(content)` | Display text | `.font_size(n)` |
| `Button(label)` | Clickable button | `.on_click(handler)` |
| `Input(initial)` | Single-line input | `.on_change(handler)` |
| `MultilineInput(state)` | Multi-line editor | `.on_change(handler)` |
| `CheckBox(state)` | Toggle checkbox | `.on_change(handler)` |
| `Slider(state)` | Range slider | `.on_change(handler)` |
| `Image(path)` | Local image | - |
| `NetImage(url)` | Remote image | - |
| `Markdown(content)` | Rich markdown | `.on_link_click(handler)` |
### Layout Containers
Arrange widgets hierarchically:
```python
from castella import Column, Row, Box
# Vertical stack
Column(
Text("Header"),
Button("Click me"),
Text("Footer"),
)
# Horizontal stack
Row(
Button("Left"),
Button("Right"),
)
# Overlapping (z-index support)
Box(
main_content,
modal_overlay.z_index(10),
)
```
## Component Pattern
Build reactive UIs with the `Component` class:
```python
from castella import Component, State, Column, Text, Button
class Counter(Component):
def __init__(self):
super().__init__()
self._count = State(0)
self._count.attach(self) # Trigger view() on change
def view(self):
return Column(
Text(f"Count: {self._count()}"),
Button("+1").on_click(lambda _: self._count.set(self._count() + 1)),
)
```
### State Management
`State[T]` is an observable value that triggers UI rebuilds:
```python
from castella import State
count = State(0) # Create with initial value
value = count() # Read current value
count.set(42) # Set new value
count += 1 # Operator support: +=, -=, *=, /=
```
### ListState for Collections
`ListState` is an observable list:
```python
from castella import ListState
items = ListState(["a", "b", "c"])
items.append("d") # Triggers rebuild
items.set(["x", "y"]) # Atomic replace (single rebuild)
```
### Multiple States Pattern
When using multiple states, attach each to the component:
```python
class MultiStateComponent(Component):
def __init__(self):
super().__init__()
self._tab = State("home")
self._counter = State(0)
# Attach each state
self._tab.attach(self)
self._counter.attach(self)
def view(self):
return Column(
Text(f"Tab: {self._tab()}"),
Text(f"Count: {self._counter()}"),
)
```
## Size Policies
Control how widgets size themselves:
| Policy | Behavior |
|--------|----------|
| `SizePolicy.FIXED` | Exact size specified |
| `SizePolicy.EXPANDING` | Fill available space |
| `SizePolicy.CONTENT` | Size to fit content |
### Fluent API Shortcuts
```python
from castella import SizePolicy
# Fixed sizing
widget.fixed_width(100)
widget.fixed_height(40)
widget.fixed_size(200, 100)
# Content sizing
widget.fit_content() # Both dimensions
widget.fit_content_width() # Width only
widget.fit_content_height() # Height only
# Fill parent
widget.fit_parent()
```
### Important Constraint
A Layout with `CONTENT` height_policy cannot have `EXPANDING` height children:
```python
# This will raise RuntimeError:
Column(
Text("Hello"), # Text defaults to EXPANDING height
).height_policy(SizePolicy.CONTENT)
# Fix by setting children to FIXED or CONTENT:
Column(
Text("Hello").fixed_height(24),
).height_policy(SizePolicy.CONTENT)
```
## Styling
### Widget Styling Methods
Chain style methods on widgets:
```python
Text("Hello")
.bg_color("#1a1b26")
.text_color("#c0caf5")
.fixed_height(40)
.padding(10)
```
### Border Styling
```python
# Show border with theme's default color (or custom color)
widget.show_border() # Use theme's border color
widget.show_border("#ff0000") # Use custom color
# Hide border (make it match background)
widget.erase_border()
```
### Theme System
Access and toggle themes:
```python
from castella.theme import ThemeManager
manager = ThemeManager()
theme = manager.current # Get current theme
manager.toggle_dark_mode() # Toggle dark/light
manager.prefer_dark(True) # Force dark mode
```
Built-in themes: Tokyo Night (default), Cupertino, Material Design 3
See `references/theme.md` for custom themes.
## Event Handling
### Click Events
```python
Button("Click me").on_click(lambda event: print("Clicked!"))
```
### Input Changes
```python
Input("initial").on_change(lambda text: print(f"New value: {text}"))
```
### Important: Input Widget Pattern
Do NOT attach states that Input/MultilineInput manages:
```python
class FormComponent(Component):
def __init__(self):
super().__init__()
self._text = State("initial")
# DON'T attach - causes focus loss on every keystroke
# self._text.attach(self)
def view(self):
return Input(self._text()).on_change(lambda t: self._text.set(t))
```
## Animation
### AnimatedState
Values that animate smoothly on change:
```python
from castella import AnimatedState
class AnimatedCounter(Component):
def __init__(self):
super().__init__()
self._value = AnimatedState(0, duration_ms=300)
self._value.attach(self)
def view(self):
return Column(
Text(f"Value: {self._value():.1f}"),
Button("+10").on_click(lambda _: self._value.set(self._value() + 10)),
)
```
### Widget Animation Methods
```python
# Animate to position/size
widget.animate_to(x=200, y=100, duration_ms=400)
# Slide animations
widget.slide_in("left", distance=100, duration_ms=300)
widget.slide_out("right", distance=100, duration_ms=300)
```
See `references/animation.md` for more animation patterns.
## Scrollable Containers
Make layouts scrollable:
```python
from castella import Column, ScrollState, SizePolicy
class ScrollableList(Component):
def __init__(self, items):
super().__init__()
self._items = ListState(items)
self._items.attach(self)
self._scroll = ScrollState() # Preserves scroll position
def view(self):
return Column(
*[Text(item).fixed_height(30) for item in self._items],
scrollable=True,
scroll_state=self._scroll,
).fixed_height(300)
```
## Z-Index Stacking
Layer widgets with z-index:
```python
from castella import Box
Box(
main_content.z_index(1),
modal_dialog.z_index(10), # Appears on top
)
```
## Semantic IDs for MCP
Assign semantic IDs for MCP accessibility:
```python
Button("Submit").semantic_id("submit-btn")
Input("").semantic_id("email-input")
```
## Best Practices
1. **Attach states**: Use `state.attach(self)` for each observable state
2. **Fixed heights in scrollable containers**: Use `.fixed_height()` for list items
3. **Preserve scroll**: Use `ScrollState` to maintain scroll position
4. **Atomic list updates**: Use `ListState.set(items)` for single rebuild
5. **Don't attach Input states**: Avoid attaching states managed by Input widgets
6. **Semantic IDs**: Add `.semantic_id()` for MCP integration
## Running Scripts
```bash
# Counter example
uv run python scripts/counter.py
# Hot reload during development
uv run python tools/hot_restarter.py scripts/counter.py
```
## Packaging
Package your Castella app for distribution:
```bash
# Install ux bundler
uv tool install ux-py
# Create executable
ux bundle --project . --output ./dist/
```
See `castella-packaging` skill for detailed options (macOS app bundles, code signing, cross-compilation).
## Reference
- `references/widgets.md` - Complete widget API
- `references/theme.md` - Theme system details
- `references/animation.md` - Animation patterns
- `references/state.md` - State management patterns
- `scripts/` - Executable examples (counter.py, form.py, scrollable_list.py)
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/counter.py
```python
"""
Basic Counter Example - Demonstrates Component pattern with State.
Run with: uv run python skills/castella-core/examples/counter.py
"""
from castella import App, Component, State, Column, Row, Text, Button
from castella.frame import Frame
class Counter(Component):
"""A simple counter component demonstrating State and event handling."""
def __init__(self):
super().__init__()
self._count = State(0)
self._count.attach(self) # Trigger view() on state change
def view(self):
return Column(
Text(f"Count: {self._count()}").font_size(24),
Row(
Button("-1").on_click(self._decrement).fixed_width(80),
Button("+1").on_click(self._increment).fixed_width(80),
),
Button("Reset").on_click(self._reset),
).padding(20)
def _increment(self, _event):
self._count += 1
def _decrement(self, _event):
self._count -= 1
def _reset(self, _event):
self._count.set(0)
if __name__ == "__main__":
App(Frame("Counter", 400, 200), Counter()).run()
```
### references/theme.md
```markdown
# Castella Theme System
Comprehensive theming with design tokens for consistent styling.
## ThemeManager
Singleton for theme management:
```python
from castella.theme import ThemeManager
manager = ThemeManager()
# Get current theme
theme = manager.current
print(f"{theme.name}, dark={theme.is_dark}")
# Toggle dark/light mode
manager.toggle_dark_mode()
# Force dark mode
manager.prefer_dark(True)
```
## Built-in Themes
### Tokyo Night (Default)
Purple/blue aesthetic with 6px rounded corners.
```python
from castella.theme import TOKYO_NIGHT_DARK_THEME, TOKYO_NIGHT_LIGHT_THEME
manager.set_dark_theme(TOKYO_NIGHT_DARK_THEME)
manager.set_light_theme(TOKYO_NIGHT_LIGHT_THEME)
```
### Cupertino
Apple-inspired design with 8px rounded corners.
```python
from castella.theme import CUPERTINO_DARK_THEME, CUPERTINO_LIGHT_THEME
manager.set_dark_theme(CUPERTINO_DARK_THEME)
manager.set_light_theme(CUPERTINO_LIGHT_THEME)
```
### Material Design 3
Google's Material design with 12px rounded corners.
```python
from castella.theme import MATERIAL_DARK_THEME, MATERIAL_LIGHT_THEME
manager.set_dark_theme(MATERIAL_DARK_THEME)
manager.set_light_theme(MATERIAL_LIGHT_THEME)
```
### Classic Castella
Original neon/pastel themes.
```python
from castella.theme import DARK_THEME, LIGHT_THEME
```
## Theme Properties
### Colors (ColorPalette)
```python
theme = manager.current
# Background colors
theme.colors.bg_canvas # Main background
theme.colors.bg_primary # Primary surface
theme.colors.bg_secondary # Secondary surface
theme.colors.bg_tertiary # Tertiary surface
# Text colors
theme.colors.text_primary # Main text
theme.colors.text_secondary # Secondary text
theme.colors.text_muted # Muted text
# Semantic colors
theme.colors.text_info # Info blue
theme.colors.text_success # Success green
theme.colors.text_warning # Warning yellow
theme.colors.text_danger # Danger red
# Border colors
theme.colors.border_primary
theme.colors.border_secondary
```
### Typography
```python
theme.typography.font_family # Default font
theme.typography.font_family_mono # Monospace font
theme.typography.base_size # Base font size (px)
theme.typography.scale_ratio # Size scaling ratio
```
### Spacing
```python
theme.spacing.padding_sm # Small padding
theme.spacing.padding_md # Medium padding
theme.spacing.padding_lg # Large padding
theme.spacing.margin_sm # Small margin
theme.spacing.margin_md # Medium margin
theme.spacing.margin_lg # Large margin
theme.spacing.border_radius # Corner radius
theme.spacing.border_width # Border width
```
## Creating Custom Themes
### Derive from Existing Theme
Partial override of an existing theme:
```python
from castella.theme import TOKYO_NIGHT_DARK_THEME
custom = TOKYO_NIGHT_DARK_THEME.derive(
colors={
"border_primary": "#00ff00",
"text_info": "#00ffff",
},
typography={"base_size": 16},
spacing={"border_radius": 12},
)
manager.set_dark_theme(custom)
```
### Create New Theme
Complete custom theme:
```python
from castella.theme import Theme, ColorPalette, Typography, Spacing
my_palette = ColorPalette(
bg_canvas="#0a0a0a",
bg_primary="#121212",
bg_secondary="#1a1a1a",
bg_tertiary="#242424",
text_primary="#ffffff",
text_secondary="#b0b0b0",
text_muted="#707070",
text_info="#64b5f6",
text_success="#81c784",
text_warning="#ffb74d",
text_danger="#e57373",
border_primary="#333333",
border_secondary="#444444",
)
my_theme = Theme(
name="my-custom-theme",
is_dark=True,
colors=my_palette,
typography=Typography(
font_family="Inter",
font_family_mono="JetBrains Mono",
base_size=14,
scale_ratio=1.25,
),
spacing=Spacing(
padding_sm=4,
padding_md=8,
padding_lg=16,
margin_sm=4,
margin_md=8,
margin_lg=16,
border_radius=8,
border_width=1,
),
code_pygments_style="monokai",
)
manager.set_dark_theme(my_theme)
```
## Widget Styles
Themes provide default styles for widgets:
```python
theme.button # Button styles by Kind and AppearanceState
theme.input # Input field styles
theme.text # Text styles
theme.scrollbar # Scrollbar styles
theme.scrollbox # Scrollbox container styles
```
## Environment Variables
Override theme at runtime:
```bash
CASTELLA_DARK_MODE=true # Force dark mode
CASTELLA_DARK_MODE=false # Force light mode
```
## Theme Demos
Run built-in theme demos:
```bash
uv run python examples/tokyo_night_theme_demo.py
uv run python examples/cupertino_theme_demo.py
uv run python examples/material_theme_demo.py
```
```
### references/animation.md
```markdown
# Castella Animation System
Smooth property animations with easing functions and reactive state transitions.
## AnimationScheduler
Singleton managing the animation tick loop (60 FPS desktop, 10 FPS TUI):
```python
from castella.animation import AnimationScheduler
scheduler = AnimationScheduler.get()
scheduler.add(tween) # Add animation
```
## Easing Functions
Available easing functions:
```python
from castella.animation import EasingFunction
EasingFunction.LINEAR # Constant speed
EasingFunction.EASE_IN # Slow start
EasingFunction.EASE_OUT # Slow end
EasingFunction.EASE_IN_OUT # Slow start and end
EasingFunction.EASE_IN_CUBIC # Cubic slow start
EasingFunction.EASE_OUT_CUBIC # Cubic slow end
EasingFunction.EASE_IN_OUT_CUBIC # Cubic slow start and end
EasingFunction.BOUNCE # Bouncy effect
```
## Tween
Animate widget properties:
```python
from castella.animation import Tween, AnimationScheduler, EasingFunction
tween = Tween(
target=my_widget,
property_name="x", # "x", "y", "width", "height"
from_value=0,
to_value=200,
duration_ms=500,
easing=EasingFunction.EASE_OUT_CUBIC,
on_complete=lambda: print("Done!"),
)
AnimationScheduler.get().add(tween)
```
## ValueTween
Generic value interpolation with callbacks:
```python
from castella.animation import ValueTween, AnimationScheduler
def on_update(value):
my_state.set(value)
tween = ValueTween(
from_value=0,
to_value=100,
duration_ms=500,
on_update=on_update,
on_complete=lambda: print("Animation complete!"),
)
AnimationScheduler.get().add(tween)
```
## AnimatedState
State wrapper that automatically animates between values:
```python
from castella import Component, Column, Text, Button
from castella.animation import AnimatedState
class Counter(Component):
def __init__(self):
super().__init__()
# Value changes animate smoothly over 200ms
self._value = AnimatedState(0, duration_ms=200)
self._value.attach(self)
def view(self):
return Column(
Text(f"Value: {self._value():.1f}"),
Button("Add 10").on_click(lambda _: self._value.set(self._value() + 10)),
)
```
### AnimatedState Control
```python
state = AnimatedState(0, duration_ms=300)
state.set(100) # Animate to value (default)
state.set(100, animate=True) # Explicit animate
state.set_immediate(100) # Set without animation
state.stop() # Stop at current value
state.finish() # Jump to target value
```
## Widget Animation Methods
Convenient animation methods on widgets:
### animate_to
Animate to target position/size:
```python
widget.animate_to(x=200, y=100, duration_ms=400)
widget.animate_to(width=300, height=200, easing=EasingFunction.BOUNCE)
```
### slide_in / slide_out
Slide animations from/to directions:
```python
# Slide in from direction
widget.slide_in("left", distance=100, duration_ms=300)
widget.slide_in("right", distance=100, duration_ms=300)
widget.slide_in("top", distance=100, duration_ms=300)
widget.slide_in("bottom", distance=100, duration_ms=300)
# Slide out to direction
widget.slide_out("left", distance=100, duration_ms=300)
widget.slide_out("right", distance=100, duration_ms=300)
```
## Animation Control
### Cancel Animation
```python
tween = Tween(...)
AnimationScheduler.get().add(tween)
# Later, cancel if needed
tween.cancel()
```
## Animation Patterns
### Fade In Effect
```python
from castella.animation import ValueTween, AnimationScheduler
def fade_in(widget, duration_ms=300):
def update_opacity(value):
widget.opacity = value
AnimationScheduler.get().add(
ValueTween(0, 1, duration_ms, on_update=update_opacity)
)
```
### Staggered Animations
```python
import time
from castella.animation import Tween, AnimationScheduler
def stagger_slide_in(widgets, delay_ms=50):
for i, widget in enumerate():
widget.x = -100 # Start off-screen
def delayed_animation(w=widget, d=i * delay_ms):
time.sleep(d / 1000)
AnimationScheduler.get().add(
Tween(w, "x", -100, 0, 300, EasingFunction.EASE_OUT)
)
threading.Thread(target=delayed_animation).start()
```
### Progress Animation
```python
from castella.animation import ValueTween, AnimationScheduler
def animate_progress(state, target_value, duration_ms=500):
current = state.value()
AnimationScheduler.get().add(
ValueTween(
current, target_value, duration_ms,
on_update=lambda v: state.set(v),
)
)
```
```
### references/widgets.md
```markdown
# Castella Widget Reference
Complete API reference for all Castella widgets.
## Text Widgets
### Text
Display single-line text.
```python
from castella import Text
text = Text("Hello, World!")
text = Text("Styled").font_size(18).text_color("#ffffff")
```
**Methods:**
- `.font_size(size: int)` - Set font size in pixels
- `.text_color(color: str)` - Set text color (hex or named)
- `.bg_color(color: str)` - Set background color
### SimpleText
Lightweight text for performance-critical lists.
```python
from castella import SimpleText
text = SimpleText("Fast text", font_size=14)
```
### MultilineText
Read-only multi-line text with selection support.
```python
from castella.multiline_text import MultilineText
text = MultilineText("Line 1\nLine 2\nLine 3", font_size=14, wrap=True)
```
**Features:**
- Mouse drag to select text
- Cmd+C/Ctrl+C to copy
- Cmd+A/Ctrl+A to select all
## Input Widgets
### Input
Single-line text input with cursor positioning.
```python
from castella import Input
from castella.core import State
text = State("")
input_widget = Input(text()).on_change(lambda t: text.set(t))
```
**Methods:**
- `.on_change(handler: Callable[[str], None])` - Text change callback
- `.placeholder(text: str)` - Placeholder text
### MultilineInput
Multi-line text editor with scrolling.
```python
from castella.multiline_input import MultilineInput, MultilineInputState
state = MultilineInputState("Initial text")
editor = MultilineInput(state, font_size=14, wrap=True)
editor = editor.height(200).height_policy(SizePolicy.FIXED)
```
**Features:**
- Scrollbar for overflow content
- Mouse wheel scrolling
- Click to position cursor
- Text selection with mouse drag
- Copy/Cut/Paste (Cmd/Ctrl+C/X/V)
- Select all (Cmd/Ctrl+A)
## Button Widgets
### Button
Clickable button with semantic kinds.
```python
from castella import Button, Kind
button = Button("Click me").on_click(lambda e: print("Clicked"))
button = Button("Danger", kind=Kind.DANGER)
```
**Kinds:**
- `Kind.NORMAL` - Default styling
- `Kind.INFO` - Blue/cyan
- `Kind.SUCCESS` - Green
- `Kind.WARNING` - Yellow/orange
- `Kind.DANGER` - Red
### CheckBox
Toggle checkbox with optional labels.
```python
from castella import CheckBox
from castella.core import State
checked = State(False)
checkbox = CheckBox(checked).on_change(lambda v: print(f"Checked: {v}"))
checkbox = CheckBox(checked, on_label="ON", off_label="OFF")
checkbox = CheckBox(checked, is_circle=True) # Circle style
```
### Switch
Toggle switch (same API as CheckBox).
```python
from castella import Switch
from castella.core import State
enabled = State(True)
switch = Switch(enabled).on_change(lambda v: print(f"Enabled: {v}"))
```
### RadioButtons
Single-select from options.
```python
from castella import RadioButtons
from castella.core import State
selected = State("option1")
radios = RadioButtons(
["option1", "option2", "option3"],
selected,
).on_change(lambda v: print(f"Selected: {v}"))
```
## Slider Widget
### Slider
Range slider with state.
```python
from castella import Slider, SliderState
state = SliderState(value=50, min_val=0, max_val=100)
slider = Slider(state).on_change(lambda v: print(f"Value: {v}"))
# Access value
print(state.value()) # 50
print(state.ratio()) # 0.5 (normalized 0-1)
state.set(75)
```
## Progress Widget
### ProgressBar
Progress indicator.
```python
from castella import ProgressBar, ProgressBarState
state = ProgressBarState(value=0, min_val=0, max_val=100)
progress = ProgressBar(state)
progress = progress.track_color("#1a1b26").fill_color("#9ece6a")
state.set(75) # Update progress
```
## Date/Time Widget
### DateTimeInput
Date and time picker with calendar.
```python
from castella import DateTimeInput, DateTimeInputState
from datetime import date
state = DateTimeInputState(
value="2024-12-25T14:30:00",
enable_date=True,
enable_time=True,
)
picker = DateTimeInput(
state=state,
label="Appointment",
min_date=date.today(),
)
# Get values
print(state.to_display_string()) # "2024-12-25 14:30"
print(state.to_iso()) # "2024-12-25T14:30:00"
```
## Image Widgets
### Image
Display local image.
```python
from castella import Image
img = Image("path/to/image.png")
```
### NetImage
Display remote image.
```python
from castella import NetImage
img = NetImage("https://example.com/image.png")
```
### AsyncNetImage
Async-loading remote image.
```python
from castella import AsyncNetImage
img = AsyncNetImage("https://example.com/image.png")
```
### NumpyImage
Display numpy array as image.
```python
from castella import NumpyImage
import numpy as np
array = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
img = NumpyImage(array)
```
## Layout Containers
### Column
Vertical layout.
```python
from castella import Column
col = Column(
widget1,
widget2,
scrollable=True, # Enable scrolling
scroll_state=state, # Preserve scroll position
)
```
### Row
Horizontal layout.
```python
from castella import Row
row = Row(widget1, widget2, widget3)
```
### Box
Overlapping layout with z-index.
```python
from castella import Box
box = Box(
background.z_index(1),
foreground.z_index(10),
)
```
## Navigation Widgets
### Tabs
Tabbed navigation.
```python
from castella import Tabs, TabsState, TabItem
state = TabsState([
TabItem(id="home", label="Home", content=home_widget),
TabItem(id="settings", label="Settings", content=settings_widget),
], selected_id="home")
tabs = Tabs(state).on_change(lambda id: print(f"Tab: {id}"))
state.select("settings") # Programmatic selection
```
### Tree
Hierarchical tree view.
```python
from castella import Tree, TreeState, TreeNode
nodes = [
TreeNode(id="docs", label="Documents", icon="📁", children=[
TreeNode(id="readme", label="README.md", icon="📄"),
]),
]
state = TreeState(nodes, multi_select=False)
tree = Tree(state).on_select(lambda node: print(node.label))
```
### FileTree
File system tree.
```python
from castella import FileTree, FileTreeState
state = FileTreeState(root_path=".", show_hidden=False, dirs_first=True)
file_tree = FileTree(state).on_file_select(lambda path: print(path))
```
## Overlay Widget
### Modal
Modal dialog overlay.
```python
from castella import Modal, ModalState, Column, Button, Text
modal_state = ModalState()
modal = Modal(
content=Column(
Text("Modal Content"),
Button("Close").on_click(lambda _: modal_state.close()),
),
state=modal_state,
title="My Modal",
)
modal_state.open() # Open modal
modal_state.close() # Close modal
```
## Data Display
### DataTable
High-performance data table.
```python
from castella import DataTable, DataTableState, ColumnConfig
state = DataTableState(
columns=[
ColumnConfig(name="Name", width=150, sortable=True),
ColumnConfig(name="Age", width=80, sortable=True),
],
rows=[["Alice", 30], ["Bob", 25]],
)
table = DataTable(state).on_sort(lambda e: print(e.column, e.direction))
```
### Markdown
Rich markdown rendering.
```python
from castella import Markdown
md = Markdown("""
# Heading
**Bold** and *italic*.
```python
print("Hello")
```
""", base_font_size=14, on_link_click=lambda url: print(url))
```
## Common Widget Methods
All widgets support these methods:
```python
widget
.width(100)
.height(50)
.width_policy(SizePolicy.FIXED)
.height_policy(SizePolicy.EXPANDING)
.fixed_width(100)
.fixed_height(50)
.fixed_size(100, 50)
.fit_content()
.fit_parent()
.bg_color("#ffffff")
.z_index(10)
.semantic_id("my-widget")
.padding(10)
```
```
### references/state.md
```markdown
# Castella State Management
Reactive state patterns for building dynamic UIs.
## State[T]
Observable value wrapper that triggers UI rebuilds:
```python
from castella import State
# Create state
count = State(0)
name = State("Alice")
items = State([1, 2, 3])
# Read value
current = count()
# Set value (triggers rebuild)
count.set(42)
# Operator shortcuts
count += 1 # Increment
count -= 1 # Decrement
count *= 2 # Multiply
count /= 2 # Divide
```
## Attaching State to Components
States must be attached to trigger `view()` rebuilds:
```python
from castella import Component, State
class Counter(Component):
def __init__(self):
super().__init__()
self._count = State(0)
self._count.attach(self) # Required!
def view(self):
return Text(f"Count: {self._count()}")
```
### Multiple States
Attach each state individually:
```python
class MultiStateComponent(Component):
def __init__(self):
super().__init__()
self._name = State("Alice")
self._age = State(30)
# Attach each one
self._name.attach(self)
self._age.attach(self)
```
### Alternative: model() for Single State
For single-state components:
```python
class Counter(Component):
def __init__(self):
super().__init__()
self._count = State(0)
self.model(self._count) # Shortcut for single state
```
## ListState
Observable list for collections:
```python
from castella import ListState
items = ListState(["a", "b", "c"])
# Mutations (each triggers rebuild)
items.append("d")
items.insert(0, "first")
items.remove("b")
items.pop()
items.clear()
# Iteration
for item in items:
print(item)
# Length
print(len(items))
# Indexing
print(items[0])
items[0] = "modified"
```
### Atomic Updates with set()
Use `set()` for batch updates (single rebuild):
```python
# BAD: Multiple rebuilds
items.clear()
for item in new_items:
items.append(item)
# GOOD: Single rebuild
items.set(new_items)
```
### Cached Widget Mapping
Preserve widget instances across rebuilds:
```python
class TodoList(Component):
def __init__(self):
super().__init__()
self._items = ListState([...])
self._items.attach(self)
def view(self):
# Widgets cached by item.id
widgets = self._items.map_cached(
lambda item: TodoItemWidget(item.id, item.text)
)
return Column(*widgets)
```
Custom key function:
```python
widgets = self._items.map_cached(
factory=lambda item: MyWidget(item),
key_fn=lambda item: item.uuid,
)
```
## Component.cache()
Alternative caching on the component:
```python
class MyComponent(Component):
def view(self):
# Cache identified by source location
widgets = self.cache(
self._items,
lambda item: TimerWidget(item.id, item.name),
)
return Column(*widgets)
```
**When to use which:**
- `ListState.map_cached()` - Simpler API, cache on ListState
- `Component.cache()` - Multiple caches in same view()
## ScrollState
Preserve scroll position across rebuilds:
```python
from castella import ScrollState, Column, SizePolicy
class ScrollableList(Component):
def __init__(self):
super().__init__()
self._items = ListState([...])
self._items.attach(self)
self._scroll = ScrollState() # NOT attached!
def view(self):
return Column(
*[Text(item).fixed_height(30) for item in self._items],
scrollable=True,
scroll_state=self._scroll, # Position preserved
).fixed_height(300)
```
### ScrollState Properties
```python
scroll = ScrollState()
scroll.x # Horizontal scroll position
scroll.y # Vertical scroll position
scroll.x = 0 # Reset horizontal scroll
scroll.y = 0 # Reset vertical scroll
```
## Input Widget State Pattern
Important: Do NOT attach states managed by Input widgets:
```python
class FormComponent(Component):
def __init__(self):
super().__init__()
self._text = State("initial")
# DON'T attach - causes focus loss!
# self._text.attach(self)
def view(self):
return Input(self._text()).on_change(
lambda t: self._text.set(t)
)
```
Same for MultilineInput:
```python
self._editor = MultilineInputState("content")
# DON'T attach
# self._editor.attach(self)
```
## SliderState
State for Slider widget:
```python
from castella import SliderState
state = SliderState(value=50, min_val=0, max_val=100)
print(state.value()) # 50
print(state.ratio()) # 0.5 (normalized 0-1)
state.set(75)
```
## ProgressBarState
State for ProgressBar widget:
```python
from castella import ProgressBarState
state = ProgressBarState(value=0, min_val=0, max_val=100)
state.set(50) # Update progress
```
## TabsState
State for Tabs widget:
```python
from castella import TabsState, TabItem
state = TabsState([
TabItem(id="home", label="Home", content=home_widget),
TabItem(id="settings", label="Settings", content=settings_widget),
], selected_id="home")
state.select("settings") # Programmatic selection
```
## ModalState
State for Modal widget:
```python
from castella import ModalState
state = ModalState()
state.open() # Show modal
state.close() # Hide modal
```
## TreeState
State for Tree widget:
```python
from castella import TreeState, TreeNode
state = TreeState(nodes, multi_select=False)
state.select("node-id") # Select node
state.expand_to("node-id") # Expand to reveal node
```
## State Observation Pattern
States implement observer pattern:
```python
from castella.core import State
count = State(0)
# Add observer
def on_change(new_value):
print(f"Value changed to: {new_value}")
count.add_observer(on_change)
# Remove observer
count.remove_observer(on_change)
```
## Lazy State Attachment
For components created before App exists:
```python
class MyComponent(Component):
def __init__(self):
super().__init__()
self._state = State(0)
self._attached = False
# DON'T attach here
def view(self):
# Attach lazily when view() called
if not self._attached:
self._state.attach(self)
self._attached = True
return Text(str(self._state()))
```
```