Skip to content
Back to skills

Castella Core

ASecurity

Build desktop, web, or terminal UIs with Castella. Create widgets, components, layouts, manage reactive state, handle events, and use the theme system.

  • 41 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 5, 2026
designpythonbashreactapi

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 8 files and shows the line behind each finding

Scanned June 5, 2026

npx -y skills add i2y/castella --skill castella-core --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Castella Core?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Castella Core
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/i2y-castella-core/badge)](https://www.skillsdirectory.com/skills/i2y-castella-core)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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()
```

### Scrollable Auto-Downgrade

Scrollable `Column`/`Row` automatically downgrade `EXPANDING` children to `CONTENT`:

```python
# 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:

```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. **Scrollable containers**: EXPANDING children are auto-downgraded; use `.fixed_height()` for precise control
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)

Files in this skill

  • SKILL.md9.5 KB
  • references/animation.md4.6 KB
  • references/state.md6.2 KB
  • references/theme.md4.6 KB
  • references/widgets.md7.5 KB
  • scripts/counter.py1.1 KB
  • scripts/form.py2.1 KB
  • scripts/scrollable_list.py2.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.