Agent skill
castella-core
Build desktop, web, or terminal UIs with Castella. Create widgets, components, layouts, manage reactive state, handle events, and use the theme system.
Install this agent skill to your Project
npx add-skill https://github.com/i2y/castella/tree/main/skills/castella-core
SKILL.md
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:
from castella import App, Text
from castella.frame import Frame
App(Frame("Hello", 800, 600), Text("Hello, Castella!")).run()
Install and run:
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 UIApp(frame, widget)- Application entry point with.run()- Frame auto-selects platform: GLFW (desktop), Web, or Terminal
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:
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:
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:
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:
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:
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
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()
Scrollable Auto-Downgrade
Scrollable Column/Row automatically downgrade EXPANDING children to CONTENT:
# Just works! No need to set size policies manually.
Column(Text("hello"), scrollable=True)
# Fluent API also works:
col = Column(Text("hello"))
col.scrollable() # Existing children are downgraded too
Important Constraint
A Layout with CONTENT height_policy cannot have EXPANDING height children:
# 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:
Text("Hello")
.bg_color("#1a1b26")
.text_color("#c0caf5")
.fixed_height(40)
.padding(10)
Border Styling
# 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:
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
Button("Click me").on_click(lambda event: print("Clicked!"))
Input Changes
Input("initial").on_change(lambda text: print(f"New value: {text}"))
Important: Input Widget Pattern
Do NOT attach states that Input/MultilineInput manages:
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:
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
# 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:
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:
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:
Button("Submit").semantic_id("submit-btn")
Input("").semantic_id("email-input")
Best Practices
- Attach states: Use
state.attach(self)for each observable state - Scrollable containers: EXPANDING children are auto-downgraded; use
.fixed_height()for precise control - Preserve scroll: Use
ScrollStateto maintain scroll position - Atomic list updates: Use
ListState.set(items)for single rebuild - Don't attach Input states: Avoid attaching states managed by Input widgets
- Semantic IDs: Add
.semantic_id()for MCP integration
Running Scripts
# 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:
# 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 APIreferences/theme.md- Theme system detailsreferences/animation.md- Animation patternsreferences/state.md- State management patternsscripts/- Executable examples (counter.py, form.py, scrollable_list.py)
Didn't find tool you were looking for?