Hi all,
I’d like to share Weaverlet, an open-source framework we built at the Observatorio Metropolitano CentroGeo (Mexico) for structuring Plotly Dash applications as a hierarchy of reusable Python classes. It’s been quietly powering our internal dashboards since 2024, and we just shipped 0.3, which modernizes the dependency stack (Dash 4, Python 3.12+) and rounds out the framework story.
Weaverlet vs. plain Dash
Plain Dash gives you primitives, not encapsulation. If you’ve ever wanted to drop the same widget into two pages of the same app, you’ve probably bumped into:
- Callbacks register globally against a module-level
app. - IDs are strings you manage by hand, and reusing one anywhere gives you
DuplicateIdError. - Reusing a widget across apps means copy-paste and rename.
Weaverlet packages layout + callbacks + IDs into a class:
from weaverlet import WeaverletComponent, WeaverletApp, Identifier
from dash_extensions.enrich import Input, Output
from dash import html, dcc
class EchoComponent(WeaverletComponent):
text_input_id = Identifier()
echo_div_id = Identifier()
def get_layout(self):
return html.Div([
dcc.Input(id=self.text_input_id, type="text"),
html.Div(id=self.echo_div_id),
])
def register_callbacks(self, app):
@app.callback(Output(self.echo_div_id, "children"),
Input(self.text_input_id, "value"))
def echo(value):
return value or ""
WeaverletApp(root_component=EchoComponent()).app.run()
Identifier() is a descriptor that returns a globally-unique string per instance. You can put EchoComponent() on the same page twice and it just works.
What you get on top of Dash
- Class-based encapsulation. Each
WeaverletComponentowns its own layout, callbacks, and IDs. Compose them into a tree and hand the root toWeaverletApp. - Auto-unique IDs via the
Identifier()descriptor. No more string juggling, no moreDuplicateIdError. - Typed inter-component events via
SignalComponent, instead of hand-rolleddcc.Store+MultiplexerTransformpatterns for every cross-widget event. - Routing, programmatic and filesystem-free, via
SimpleRouterComponent. - Session-based auth gating via
AuthRouterComponentbacked by Flask sessions. - Shared context propagated to every component in the DAG, so configuration and services flow down cleanly without prop drilling.
Under the hood, WeaverletApp.app is a dash_extensions.enrich.DashProxy, so everything in the Dash ecosystem (DBC, DMC, dash-leaflet, dash-ag-grid, etc.) keeps working. You can also use AIO widgets as leaves inside a WeaverletComponent.get_layout() without friction.
Weaverlet vs. Dash Pages and AIO
These solve adjacent problems:
- Dash Pages is a filesystem routing convention.
- AIO is a widget pattern (one reusable widget, pattern-matching IDs).
- Weaverlet is a whole-app framework: a tree of classes with lifecycle, routing, signals, and shared context.
There’s a side-by-side comparison page in the docs (under “Weaverlet vs Dash Pages/AIO”) if you want the long form.
Links
- Repo: GitHub - observatoriogeo/weaverlet: OOP for Plotly Dash. Reusable component classes, auto-unique IDs, typed events, and state-preserving routing. · GitHub
- Docs: https://weaverlet.observatoriogeo.mx
- PyPI:
pip install weaverlet(package) - Examples gallery: 14 self-contained scripts ordered by complexity, from Hello World to a multipage Dash Mantine app (gallery)
- For LLM coding assistants: the docs ship a
ReadMe.LLM.mdand anllms.txtso Cursor / Claude Code / Codex can pick up Weaverlet conventions automatically.
Requires Python 3.12+ and Dash 4.1+. MIT-licensed.
I’d love feedback, especially from anyone whose Dash app has outgrown a single file and is starting to feel the limits of global callbacks and string IDs. Also happy to discuss design choices, or how it interacts with Dash Pages if you’re considering a migration path.
Thanks for reading!