Anywidgets¶
Marimo's anywidget integration lets you author
fully interactive UI components as small ES modules. On a live marimo
notebook these render inside a marimo kernel that mediates Python ↔ JS
state; on a static marimo-book page, there's no kernel. marimo-book
makes anywidgets render anyway via a small runtime shim that's loaded
on every page.
How it works¶
- During build,
marimo export ipynboutputs each anywidget as a<marimo-anywidget>custom element with the widget's ES module inlined as a base64 data URL on thedata-js-urlattribute. - The preprocessor rewraps it as
<div class="marimo-book-anywidget" data-js-url="...">. - At page load, a ~150-line JS shim
(
marimo_book.js, bundled viaextra_javascript) finds each mount, dynamically imports the module viaimport(), builds a minimal anywidget-compatiblemodelobject, and callsmodule.default.render({model, el}).
No marimo runtime needed. No WebSocket. Just one JS file and the widget's own ES module.
Seeding widget state¶
Anywidget JS modules typically read initial state via model.get("key").
Since there's no live kernel to provide that state, marimo-book has
to seed it before render() is called.
Two precedence layers — both optional; whichever values exist at each layer are merged (later wins):
1. widget_defaults in book.yml¶
widget_defaults:
CompassWidget:
b0: 3.0
PrecessionWidget:
b0: 3.0
flip_angle: 90.0
t1: 0.0
t2: 0.0
show_relaxation: false
paused: false
One entry per widget class name. Recommended when multiple cells instantiate the same widget and you want them all to share the same defaults.
2. Literal kwargs in the cell¶
@app.cell
def _(mo):
mo.ui.anywidget(PrecessionWidget(flip_angle=30.0, show_relaxation=True))
return
marimo-book walks the cell's AST, finds the widget constructor call
(any CamelCase class ending in Widget, View, or Mount), and
extracts literal kwargs (int, float, bool, str, None, list,
dict). These override widget_defaults for that specific mount.
Troubleshooting¶
"Nothing renders, but I see a placeholder div in DevTools."
- Check the browser console for an import error. A typo in the widget's data URL would show as a syntax error.
- Confirm that
javascripts/marimo_book.jsloaded (Network tab).
"The widget renders but throws Cannot read properties of undefined
in an animation loop."
- The widget's JS is reading a
model.get("key")that isn't seeded. Either add that key towidget_defaultsinbook.yml, or make the widget JS defensive withmodel.get("key") ?? defaultValue.
"I want the widget to persist state between page navigations."
- State lives in the mount
<div>; Material's instant navigation re-runs the shim on every page load. For state that needs to persist, store it inlocalStoragefrom inside your widget's JS.
Elements we strip¶
Marimo's <marimo-ui-element> wrappers around standalone controls —
<marimo-slider>, <marimo-switch>, <marimo-dropdown>,
<marimo-radio>, <marimo-number>, <marimo-button> — require a
running kernel to be meaningful. marimo-book strips them at preprocess
time. For static pages, use an anywidget that includes its controls
inside the widget itself.
Example: dartbrains widgets¶
Dartbrains ships ten Canvas 2D
and Three.js anywidgets (compass, magnetization, precession, spin
ensemble, k-space, encoding, convolution, transform cube, cost function,
smoothing) that render live on static pages with this pipeline. Its
book.yml widget_defaults block is a good template to copy.