Skip to main content
Weaverlet

OOP for Plotly Dash. Make your dashboards composable.

pip install weaverlet

Class-based encapsulation

Layout, callbacks, state, and IDs live together in one class. Instantiate twice for two independent copies that don't step on each other.

Components →

Auto-unique IDs

The Identifier() descriptor generates a unique Dash ID per instance. No more managing string IDs by hand or hitting DuplicateIdError.

Identifier →

Multi-page routing

SimpleRouterComponent maps URL paths to component instances. Built-in 404 handling, optional auth gating, clean composition. No filesystem conventions to learn.

Routing →

Multi-page apps without the wiring

Declare a route dict that maps paths to component instances. The router handles the rest: matching, 404 fallback, URL kwargs. Add AuthRouterComponent to gate routes behind Flask sessions.

from weaverlet import WeaverletApp, SimpleRouterComponent

router = SimpleRouterComponent(
routes={
"/": HomePage(),
"/about": AboutPage(),
"/docs": DocsPage(),
},
not_found_page_component=NotFoundPage(),
)
WeaverletApp(root_component=router).app.run()

Class-based encapsulation, server-side

Every UI block is a WeaverletComponent that owns its layout, callbacks, and IDs. Instantiate it twice and Dash treats the copies as independent. Same React mental model, pure Python.

from weaverlet import WeaverletComponent, WeaverletApp, Identifier
from dash_extensions.enrich import Input, Output
from dash import html, dcc


class EchoComponent(WeaverletComponent):
text_id = Identifier()
echo_id = Identifier()

def get_layout(self):
return html.Div([
dcc.Input(id=self.text_id),
html.Div(id=self.echo_id),
])

def register_callbacks(self, app):
@app.callback(Output(self.echo_id, "children"),
Input(self.text_id, "value"))
def echo(v): return v or ""

Auto-unique IDs that never collide

The Identifier() descriptor generates a unique Dash ID per instance, computed lazily and cached. You stop managing string IDs by hand and stop hitting DuplicateIdError.

class MyButton(WeaverletComponent):
btn_id = Identifier()

def get_layout(self):
return html.Button("Click me", id=self.btn_id)


# Two instances, two unique Dash IDs, zero collisions.
btn_a = MyButton()
btn_b = MyButton()

Signal-based events, not dcc.Store plumbing

SignalComponent plus typed adapters (SignalInput, SignalOutput, SignalTrigger) replace ad-hoc dcc.Store wiring as the cross-component event bus. Multiple producers and consumers work transparently thanks to DashProxy under the hood.

from weaverlet import SignalComponent, SignalOutput, SignalInput

# Producer: emit a payload dict to the signal.
@app.callback(SignalOutput(self.sig), Input(self.btn, "n_clicks"))
def emit(n):
return {"clicks": n}


# Consumer: any component, anywhere in the DAG.
@app.callback(Output(self.label, "children"), SignalInput(self.sig))
def show(payload):
return f"Got: {payload}"

Auth routing with Flask sessions

AuthRouterComponent gates routes behind flask.session. Unauthenticated visitors are redirected to the login route; on success, your login callback sets a session key and the user is bounced back to the page they were trying to reach.

from weaverlet import AuthRouterComponent

router = AuthRouterComponent(
routes={
"/": {"component": Dashboard(), "login_required": True},
"/login": {"component": LoginPage(), "login_required": False},
},
not_found_page_component=NotFound(),
)

Built for LLM coding assistants

Weaverlet ships a ReadMe.LLM.md following the ReadMe.LLM methodology plus an llms.txt at the repo root following the llmstxt.org convention. Drop a CDN URL into Claude, GPT, Codex, or Cursor and your assistant writes idiomatic Weaverlet code without guessing the API. See the full breakdown.

# Latest (tracks main):
https://cdn.jsdelivr.net/gh/observatoriogeo/weaverlet@main/ReadMe.LLM.md

# Pinned to v0.3.0:
https://cdn.jsdelivr.net/gh/observatoriogeo/weaverlet@v0.3.0/ReadMe.LLM.md

Get started

pip install weaverlet