smithery.ai

castella-core

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

First seen Apr 21, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,688 B
  • docs SUMMARY.md 172 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

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 UI
  • App(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 .onlinkclick(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

  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

# 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 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)