Skip to main content

Weaverlet

OOP for Plotly Dash — make your dashboards composable.

Vanilla Dash and Dash Pages give you primitives, not encapsulation: callbacks live globally, IDs are strings you manage by hand, and the same widget can't be dropped into two apps without surgery. Weaverlet packages layout + callbacks + state into reusable Python classes, with auto-unique IDs, typed inter-component signals, and a keep_mounted router that preserves WebGL state across navigation.

PyPI Python 3.12+ License: MIT


Weaverlet, developed by the Observatorio Metropolitano CentroGeo, turns a Plotly Dash app into a hierarchy of reusable Python classes. Each WeaverletComponent owns its own layout, callbacks, and identifiers; you compose them into a tree, hand the root to WeaverletApp, and ship.

It's aimed at:

  • Data scientists whose dashboard has outgrown a single file and needs structure.
  • Dash developers who want true component reuse instead of copy-paste between apps.
  • React-style developers looking for the same class-based component paradigm, server-side, in pure Python.

What Weaverlet adds on top of vanilla Dash

  • Class-based encapsulation — layout, callbacks, state, and IDs live together in one class. Instantiate it twice for two independent copies that don't step on each other.
  • Auto-unique IDs — the Identifier() descriptor generates a Dash-unique ID per instance. No more managing string IDs by hand or hitting DuplicateIdError when reusing a widget.
  • Signal-based eventsSignalComponent + SignalInput/SignalOutput/SignalTrigger replace ad-hoc dcc.Store + manual callback wiring as the canonical inter-component event bus.
  • State-preserving routing (new in 0.3)SimpleRouterComponent(keep_mounted=True) keeps every route mounted and toggles CSS, so WebGL canvases (dash-leaflet maps, dash-sylvereye graphs, Plotly WebGL) and React state (n_clicks, selections, scroll position) survive navigation.
  • Session-based auth routingAuthRouterComponent gates routes via flask.session out of the box.
  • Shared context — one dict propagates to every component in the DAG; no prop-drilling, no globals.

Weaverlet vs Dash Pages

Dash Pages (built into Dash 2.5+) and Weaverlet both solve "multi-page Dash" but at different levels of abstraction. Pages is a routing convention — drop a file in pages/, register it, get a route. Weaverlet is a component framework — write classes, compose them, navigate without losing state.

Capability Dash Pages Weaverlet
Built-in to Dash ✓ (Dash ≥ 2.5) One pip install
Routing source Filesystem (pages/*.py) Programmatic routes dict, computed at runtime
Path templating (/users/<id>) Via pathname / search parsing
Page-level encapsulation One module per page Reusable component classes; same class can power N routes
Cross-page widget reuse Imports + manual callback wiring Instantiate the same class anywhere; auto-unique IDs
Inter-component events dcc.Store + global callbacks SignalComponent typed events
State across navigation Unmount + remount on every route change keep_mounted=True preserves React + WebGL state
Auth gating DIY AuthRouterComponent + Flask sessions
Auto-discovered nav menu ✓ (dash.page_registry) Build it yourself

Reach for Dash Pages when your dashboard is content-heavy, each page is roughly independent, you want filesystem-driven routing, and you don't need to preserve state across navigation. It's the simpler tool and ships with Dash.

Reach for Weaverlet when you have shared widget logic across pages, when WebGL components (maps, network graphs, 3D plots) shouldn't reset on navigation, when you want typed inter-component events instead of dcc.Store plumbing, or when your dashboard has grown past the "one file per page" model and needs proper encapsulation.

The two aren't mutually exclusive — Weaverlet apps can coexist alongside a Pages-based site in the same Dash deployment if that fits your migration path.

Weaverlet vs Dash All-in-One Components

All-in-One Components (AIO, built into Dash 2+) and Weaverlet share a goal — bundle a Dash widget's layout and callbacks into one unit — but they operate at different scopes. AIO is a widget pattern: subclass html.Div, register pattern-matching callbacks once at module level, give each instance a UUID aio_id. Weaverlet is a whole-app framework: a tree of components with lifecycle hooks, routing, signals, and shared context.

Capability Dash AIO Weaverlet
Built-in to Dash ✓ (Dash ≥ 2.0) One pip install
ID style Pattern-matching dicts ({'component': ..., 'aio_id': uuid}) Flat strings via Identifier() descriptor
Callback registration Once at module level via MATCH / ALL Per instance, in register_callbacks(self, app)
Instance state on self Awkward (callbacks are module-level free functions) Native (callbacks close over self)
Composing hierarchies Limited — each AIO is typically a leaf DAG of nested components; parent/child wiring is automatic
Multipage routing Not provided (pair with Dash Pages or DIY) SimpleRouterComponent + AuthRouterComponent
Inter-component events Pattern-matching callbacks or DIY dcc.Store Typed SignalComponent events
State across navigation Up to surrounding router (none by default) keep_mounted=True preserves React + WebGL state
Shared context / config DIY Built-in dict propagated to every component
Auth gating DIY AuthRouterComponent + Flask sessions

Reach for AIO when you're building a single reusable widget — a fancy date picker, a search box, a chart-with-controls — to drop into existing Dash apps, especially when you want many instances and need to operate on all of them at once via pattern-matching. It's the lighter pattern for one widget.

Reach for Weaverlet when you're structuring a full application — multipage, auth, signal flows, shared context, state-preserving navigation. It's the framework for the whole dashboard.

They coexist nicely. Weaverlet components are regular Dash components under the hood, so you can use AIO widgets as leaves inside a WeaverletComponent.get_layout() without friction. The reverse (Weaverlet inside an AIO) is technically possible but usually awkward — Weaverlet wants to own the lifecycle of its own tree.

What's new in 0.3.0

Released April 2026 — full notes in CHANGELOG.md.

  • Dash 4.x support; Python ≥ 3.12; dependency pins relaxed (Flask / werkzeug now resolved transitively, no longer hard-pinned).
  • SimpleRouterComponent(keep_mounted=True, preserve_path="/...") for state preservation across route changes.
  • Top-level importsfrom weaverlet import WeaverletComponent, ... works directly; no need to drill into weaverlet.base / weaverlet.components.
  • Permissive get_layout signatures — components declare any subset of (pathname, hash, href, search, user, protected_route) and the router passes only what's declared.
  • DashProxy under the hood so dash_extensions.javascript.assign(), Serverside(...) return wrappers, and group= multi-output callbacks all work without extra wiring.
  • assets_folder auto-resolves to the user's main script directory — no manual asset path setup needed.
  • 60-test pytest suite; build via pyproject.toml + hatchling.
  • jupyter-dash moved to an optional extra (pip install weaverlet[jupyter]).

Install

pip install weaverlet

(or uv add weaverlet if you're on uv.)

Optional extras:

Extra Pulls in Use when
weaverlet[examples] dash-bootstrap-components, dash-mantine-components Running the bundled examples 11–14
weaverlet[dev] pytest, pytest-playwright Hacking on Weaverlet itself

Jupyter: Dash 4 has built-in Jupyter support, no extra needed. Construct your WeaverletApp normally and pass jupyter_mode='inline' (or 'tab' / 'external') to .app.run():

wapp = WeaverletApp(root_component=...)
wapp.app.run(jupyter_mode='inline')

Quick start

A single-page app that echoes user input:

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(text_value):
            return text_value or ""


WeaverletApp(root_component=EchoComponent()).app.run()

A multipage app with WebGL-safe state preservation:

from weaverlet import (
    WeaverletComponent, WeaverletApp, SimpleRouterComponent,
)
from dash import html


class HomePage(WeaverletComponent):
    def get_layout(self):
        return html.Div("Home — your dash-leaflet map goes here.")


class AboutPage(WeaverletComponent):
    def get_layout(self):
        return html.Div("About — switch back to Home; the map's zoom survives.")


class NotFound(WeaverletComponent):
    def get_layout(self):
        return html.Div("404")


router = SimpleRouterComponent(
    routes={"/": HomePage(), "/about": AboutPage()},
    not_found_page_component=NotFound(),
    keep_mounted=True,        # keep all routes mounted; toggle CSS only
    preserve_path="/",        # which route owns the document flow (default: first)
)
WeaverletApp(root_component=router).app.run()

Aliasing — multiple paths pointing at the same component instance — works in both modes. Under keep_mounted=True, aliased paths automatically share a single wrapper, so the same Identifier-bearing layout never mounts twice.

Examples

The examples/ folder has 14 self-contained scripts, ordered by complexity:

# File Demonstrates
01 01_helloworld_app.py Minimal layout-only component
02 02_echo_app.py Layout + callbacks + Identifier
03 03_greeting_app.py Constructor-injected state
04 04_router_app.py Multipage with SimpleRouterComponent
05 05_auth_router_app.py Session-protected routes via AuthRouterComponent
06 06_redirect_app.py Programmatic redirect from a callback
07 07_signal_input.py Signals carrying a payload
08 08_signal_trigger.py Edge-only triggers
09 09_signal_chain_app.py Chained signal pipeline
10 10_div_signal_trigger_app.py Signal-driven <div> updates
11 11_dbc_app.py Single-page Weaverlet + Dash Bootstrap
12 12_dbc_multipage_app.py Multipage with DBC navbar
13 13_dbc_modal_app.py DBC modal triggered by signals
14 14_dmc_multipage_app.py Dash Mantine + AppShell + keep_mounted state preservation

Compatibility

Python ≥ 3.12
Dash ≥ 4.1.0, < 5.0.0
dash-extensions ≥ 1.0.0, < 3.0.0 (1.x and 2.x both supported)

For LLM coding assistants

Weaverlet ships a ReadMe.LLM.md following the ReadMe.LLM methodology — rules, context, 14 worked examples, API signatures, and patterns specifically formatted for LLMs like Claude, GPT, and Codex. To use it from another project, drop one of these URLs into your prompt or have your assistant fetch it:

There's also an llms.txt at the repo root following the llmstxt.org convention — a short index that tools like Cursor and Claude Code can auto-discover.

Contributing & support

  • Bug reports and feature requests: GitHub issues.
  • Tests: pip install -e ".[dev]" then pytest (60 tests, ~1 second).
  • Migrating from 0.2.x? See CHANGELOG.md — most code keeps working unchanged; the main gotcha is app.run_server()app.run() (Dash 4 removed the old name).

License

Weaverlet is open-source software licensed under the MIT license.

Release files for weaverlet 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for weaverlet 0.3.1
File Size Uploaded
weaverlet-0.3.1.tar.gz 27.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for weaverlet 0.3.1
File Interpreter ABI Platform
weaverlet-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 48.9 kB

Release files / weaverlet-0.3.1.tar.gz

Download URL weaverlet-0.3.1.tar.gz
Size 27.2 kB
Tags Source
SHA-256 checksum
How to use checksums
cd88e81e1674de5c1fc75a5abe1d1adfb3cbacc7a6f0d52263e87956c549d1e9
BLAKE2b-256 checksum
How to use checksums
709e29087c56e51218f65544af98c2f86ce1a26f95de3b6ba0c45cc231bef90a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / weaverlet-0.3.1-py3-none-any.whl

Download URL weaverlet-0.3.1-py3-none-any.whl
Size 21.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a484624860f44991d2597d756d6f5f5259b5251e285c55ee91ba60794e13fb7
BLAKE2b-256 checksum
How to use checksums
037c33ce40e9107b6c0fad36409b58bd1d6ad0ac71b646c350a459f6ceadb9b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page