High-performance reactive web framework for Python.
Build dashboards, internal tools, and data apps with a simple API β real WebSocket reactivity, not full-page reruns.
Features Β· Quick Start Β· Examples Β· Components Β· Architecture Β· Static Builds Β· Contributing
v2.0 β Complete rewrite with signal-based reactivity, session-scoped state, plugin hooks, and a Streamlit-like API that doesn't re-run your entire script.
pip install cacaoimport cacao as c
c.title("Hello, Cacao!")
c.text("Welcome to the simplest Python web framework")cacao run app.pyThat's it β 3 lines of Python and you have a live web app with hot reload.
| Feature | Cacao | Streamlit |
|---|---|---|
| Reactivity | Signal-based (only changed values update) | Full script re-run on every interaction |
| State | Session-scoped by design | st.session_state bolt-on |
| Updates | WebSocket streaming (instant) | HTTP polling (laggy) |
| Multi-user | Isolated sessions built-in | Shared state issues |
| API | Context managers + decorators | Magic globals |
| Static export | cacao build β no server needed |
Not supported |
count = c.signal(0, name="count")
@c.on("increment")
async def increment(session, event):
count.set(session, count.get(session) + 1)Signals are the core of Cacao. When a value changes, only the affected components update β no full-page reruns, no diffing your entire tree.
Layouts, forms, charts, display elements, navigation β everything you need out of the box.
with c.page("/"):
c.title("Home")
with c.page("/settings"):
c.title("Settings")Hash-based routing works both in server mode and static builds.
cacao build app.pyExport your app to a static site (HTML + JS + CSS) that runs entirely in the browser. Deploy to GitHub Pages, Netlify, or any static host β no Python server required.
Extensible architecture with plugin registration, authentication hooks, and notification toasts.
c.config(theme="dark") # or "light"import cacao as c
c.config(title="Counter")
count = c.signal(0, name="count")
@c.on("increment")
async def increment(session, event):
count.set(session, count.get(session) + 1)
@c.on("decrement")
async def decrement(session, event):
count.set(session, count.get(session) - 1)
c.title("Counter")
with c.card():
c.metric("Count", count)
with c.row():
c.button("-", on_click="decrement", variant="secondary")
c.button("+", on_click="increment")import cacao as c
c.config(title="Sales Dashboard", theme="dark")
sales = c.sample_sales_data()
c.title("Sales Dashboard")
with c.row():
c.metric("Revenue", "$45,231", trend="+20.1%", trend_direction="up")
c.metric("Orders", "1,247", trend="+12.5%", trend_direction="up")
c.metric("Customers", "842", trend="+5.3%", trend_direction="up")
with c.row():
with c.col(span=8):
with c.card("Revenue Trend"):
c.line(sales, x="date", y="revenue", area=True)
with c.col(span=4):
with c.card("By Category"):
c.pie(sales[:5], values="revenue", names="category", donut=True)
with c.card("Recent Transactions"):
c.table(sales[:10], columns=["date", "category", "revenue", "orders"])More examples in the examples/ directory:
| Example | Description |
|---|---|
simple/hello.py |
Minimal hello world |
simple/counter.py |
Interactive counter with signals |
simple/metrics.py |
KPI dashboard |
simple/dashboard.py |
Full dashboard with charts |
chat/app.py |
Chat application |
todo/server.py |
Todo list app |
analytics_dashboard/ |
Analytics dashboard |
| Component | Description |
|---|---|
row() |
Horizontal flex layout |
col(span=N) |
Column with 12-grid span |
grid() |
CSS grid layout |
container() |
Centered container |
stack() |
Vertical stack |
split() |
Side-by-side split |
hero() |
Hero section |
card() |
Card container |
tabs() / tab() |
Tabbed content |
accordion() |
Collapsible sections |
modal() |
Modal dialog |
panel() |
Sliding panel |
app_shell() |
Full app layout with sidebar |
| Component | Description |
|---|---|
title() / text() |
Typography |
markdown() |
Markdown with optional TOC |
code() |
Syntax-highlighted code |
metric() |
KPI metric with trend |
table() |
Data table |
json_view() |
Interactive JSON viewer |
badge() / alert() |
Status indicators |
progress() |
Progress bar |
image() / video() |
Media |
timeline() |
Timeline display |
diff() |
Side-by-side diff viewer |
breadcrumb() |
Navigation breadcrumb |
| Component | Description |
|---|---|
button() |
Button with variants |
input_field() |
Text input |
textarea() |
Multi-line input |
select() |
Dropdown select |
checkbox() / switch() |
Toggle inputs |
slider() |
Range slider |
date_picker() |
Date picker |
search_input() |
Search with suggestions |
file_upload() |
File upload |
chat() |
Chat input interface |
| Component | Description |
|---|---|
line() / area() |
Line and area charts |
bar() |
Bar chart |
pie() / donut() |
Pie and donut charts |
scatter() |
Scatter plot |
gauge() |
Gauge meter |
heatmap() |
Heatmap |
radar() |
Radar chart |
funnel() |
Funnel chart |
treemap() |
Treemap |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PYTHON SERVER β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββββ β
β β Signals β β Sessions β β Events β β WebSocket/HTTPβ β
β β (state) ββββ (per- ββββ (typed ββββ Server β β
β β β β client) β β async) β β (Starlette) β β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββ¬ββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββ
β JSON
β WebSocket
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββ
β REACT CLIENT β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββ΄βββββββ β
β β Hooks β βComponentsβ β Event β β State β β
β βuseCacao()ββββ (60+ UI) ββββDispatcherββββ Store β β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Data flow:
Python API β JSON definition β WebSocket β React renders
β β
Signal.set() βββ Event handler βββ User action
cacao run app.py # Run with hot reload
cacao run app.py --port 3000 # Custom port
cacao run app.py --no-reload # Disable hot reload
cacao create my-dashboard # Scaffold a new project
cacao build app.py # Build static site
cacao build app.py --base-path /repo # For GitHub Pages subdirectoryCacao generates static sites that run entirely in the browser β no Python server required.
cacao build app.pyOutput:
dist/
βββ index.html # Your app
βββ cacao.js # Runtime + built-in handlers
βββ cacao.css # Styles
Built-in client-side handlers for common operations:
- Encoders β Base64, URL, HTML entities, JWT decode
- Generators β UUID, passwords, Lorem Ipsum
- Converters β JSON/YAML, case conversion, number bases
- Text β Statistics, regex testing
- Crypto β Hash generation, HMAC
Deploy to GitHub Pages:
# .github/workflows/deploy.yml
- run: pip install cacao
- run: cacao build app.py --base-path /${{ github.event.repository.name }}See cacao-tools for a full static site example.
| Layer | Technology |
|---|---|
| Backend | Python 3.10+, Starlette, Uvicorn, WebSocket |
| Frontend | React 18, Chart.js, LESS |
| Build | esbuild, hatchling |
| State | Signal-based reactivity with session scoping |
| Deployment | Static export, GitHub Pages, any ASGI server |
cacao/
βββ __init__.py # Package exports
βββ simple.py # Streamlit-like API (import cacao as c)
βββ server/ # Core server
β βββ ui.py # 60+ UI components
β βββ chart.py # Chart components
β βββ signal.py # Signal & Computed reactivity
β βββ session.py # Session management
β βββ events.py # Event handling
β βββ server.py # HTTP + WebSocket server
β βββ data.py # Data utilities
βββ cli/ # CLI (run, build, create)
βββ frontend/ # Frontend source (devs only)
β βββ src/components/ # React components
β βββ src/styles/ # LESS styles
β βββ src/handlers/ # Static build handlers
β βββ dist/ # Build output
βββ examples/ # Example apps
Contributions are welcome! See the repo issues for ideas.
fork β feature branch β commit β push β pull request
Cacao β Reactive Python UIs, made simple.
Made with care by Juan Denis
