BETA

Python · Django · FastAPI · HTMX

Server-side components
with LiveView-style
interactivity.

Build reactive UIs entirely in Python. No React, no Redux, no build step — HTMX handles the browser side; your components own the state.

Framework-agnostic core FastAPI + Django adapters 5 KB JS footprint Pure Python tests
API Reference → Live-view example GitHub
Install
$uv pip install -e ".[django,websockets]"

capabilities

Everything you need. Nothing you don't.

A complete server-component system with zero mandatory JavaScript and a pure-Python test story.

Server-Driven UI
State lives on the server. The browser sends events and receives rendered HTML — no client-side state machine needed.
🔒
Permission System
permission_classes = [IsAuthenticated] on the component class. Enforced by every view automatically. JSON 401/403, no redirects.
🚀
Optimistic UI
Override get_optimistic_patch() to return an instant state delta. The client rolls back automatically on error.
🌐
WebSocket Push
Real-time component updates via Django Channels or FastAPI WebSocket. Broadcast to groups; the framework handles the render cycle.
🧩
Component Composition
SlotComponent and CompositeComponent let you build complex pages from named, reusable pieces.
🧪
Pure-Python Testing
ComponentTestCase lets you mount, dispatch events, and assert state — no HTTP, no browser, no running server.
Rate Limiting
Mix in RateLimitMixin on any view. Sliding-window, per-user. Returns HTTP 429 with Retry-After header.
💾
Caching
CacheMixin caches render output per component + params. Event requests bypass the cache automatically.
🗃
Django Model Binding
DjangoModelComponent binds state fields to ORM instances. save_instance() handles validation and transactions.

Same result. A fraction of the complexity.

Building a real-time cart in React requires Redux, async thunks, a store provider, serialisation, and a bundler. In component-framework it's one Python class.

// REACT + REDUX
cart.ts TypeScript
// 1. async thunk
const addToCart = createAsyncThunk(
  'cart/addItem',
  async (item, { dispatch }) => {
    const r = await fetch('/api/cart/', {
      method: 'POST',
      body: JSON.stringify(item),
      headers: { 'X-CSRFToken': getCsrf() },
    });
    return r.json();
  }
);

// 2. reducer
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: '0.00', status: 'idle' },
  extraReducers: (builder) => {
    builder
      .addCase(addToCart.pending,   s => { s.status = 'loading' })
      .addCase(addToCart.fulfilled, (s, a) => {
        s.items  = a.payload.items;
        s.total  = a.payload.total;
        s.status = 'idle';
      })
      .addCase(addToCart.rejected,  s => { s.status = 'error' });
  },
});

// 3. optimistic hook (React 19)
function CartButton({ item }) {
  const [optimisticItems, addOptimistic] =
    useOptimistic(items, (state, newItem) =>
      [...state, { ...newItem, pending: true }]
    );
  /* …plus store provider, types, bundler config */
}
// COMPONENT FRAMEWORK
cart/components.py Python
@registry.register("cart")
class CartComponent(Component):

    def on_add_item(self,
                   product_id: int,
                   size: str):
        """Add item or increment qty."""
        for item in self.state["items"]:
            if (item["product_id"] == product_id
                    and item["size"] == size):
                item["qty"] += 1
                self._recalculate_total()
                return
        self.state["items"].append({
            "product_id": product_id,
            "size":       size,
            "qty":        1,
        })
        self._recalculate_total()

    def get_optimistic_patch(self,
                             event: str,
                             payload: dict) -> dict | None:
        if event == "add_item":
            return {"items":
                self.state["items"]
                + [{**payload, "qty": 1}]}
        return None

# That's it. pytest covers the rest.
Concern React + Redux Component Framework
State locationuseState / Redux storeself.state dict on server
Sync strategyREST polling / WS eventsBuilt-in server state
Optimistic UIuseOptimistic() + rollbackget_optimistic_patch()
Auth gatingMiddleware + useSession()permission_classes = [IsAuthenticated]
Build stepwebpack / Vite requiredNone
JS payload150–400 KB gzipped~5 KB (htmx + client)
Test toolingJest, Storybook, RTLpytest — pure Python

quick start

Up and running in four steps.

01 / Install
Add to your project
Install with uv or pip. Django and WebSocket support are optional extras.
uv pip install -e ".[django,websockets]"
02 / Define
Write a component
Extend Component, register it, implement lifecycle methods.
@registry.register("counter")
class Counter(Component):
    def mount(self):
        self.state["count"] = 0

    def on_increment(self):
        self.state["count"] += 1
03 / Wire
Connect to your framework
Mount the component view in Django urls.py or FastAPI router — one line.
# Django
path("components/<str:name>/",
     component_view)

# FastAPI
app.include_router(
    component_router)
04 / Render
Add HTMX to your template
HTMX drives the event loop. No JavaScript framework needed.
<!-- counter.html -->
<div id="counter">
  {{ state.count }}
  <button
    hx-post="/components/counter/"
    hx-vals='{"event":"increment"}'
    hx-target="#counter"
    hx-swap="outerHTML">
    +1
  </button>
</div>

Where to go next.