tkipw
Run ipywidgets / anywidget on the desktop — no Jupyter Notebook, no browser tab.
tkipw is a small runtime that hosts ipywidgets and anywidget inside a real system WebView embedded in a Tkinter window, powered by tkwry.
Python → ipywidgets API → Comm (tkipw) → tkwry IPC → JS Widget Manager → DOM
Alpha — APIs and behavior may change. Not recommended for production yet.
📖 Overview
Jupyter widgets normally need a notebook kernel and a browser. tkipw drops both:
your widgets run in the same process as your Python code and render in a native
WebView that lives inside a Tk Frame (via tkwry's child-window embedding).
- No notebook — plain
python your_script.py - Real widgets — the official
@jupyter-widgets/html-managerruns the same controls you use in Jupyter - anywidget — bundled front end (e.g. Plotly
FigureWidget) - ipyleaflet — bundled live Leaflet widget module (Python ↔ map trait updates)
- ipycanvas — bundled Canvas widget module (Python drawing commands ↔ WebView)
- bqplot — bundled interactive plotting widgets (Python ↔ SVG figure updates)
- ipympl — bundled interactive Matplotlib canvas (opt in with
import ipympl) - Notebook-like display —
display(),clear_output(),Output,plt.show(), tracebacks, andloggingall show up in an output area - One event loop — everything runs on Tk's
mainloop
🔧 Requirements
- Python 3.10+
- tkwry 0.1.4+ (system WebView: WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux — see tkwry's platform notes)
- ipywidgets 8.x
The widget core (runtime.js) is hosted over loopback; leaflet / ipycanvas /
bqplot / ipympl packs are fetched the first time a matching widget loads. The
Playground's Monaco editor and standalone Altair / Bokeh documents load their
JavaScript libraries from a CDN.
Bundled front-end dependencies
The widget front end is prebuilt from js/ with esbuild into
src/tkipw/html/runtime.{js,css} plus lazy pack-*.{js,css} files and shipped
inside the wheel (built in CI, not committed to the repo). It embeds:
- Jupyter Widgets (
@jupyter-widgets/*) and Lumino — BSD-3-Clause - anywidget, jupyter-leaflet, jQuery, Backbone.js — MIT
- ipycanvas (and Rough.js) — BSD-3-Clause / MIT
- bqplot / bqscales (and D3) — Apache-2.0 / ISC
- ipympl / jupyter-matplotlib — BSD-3-Clause
- Leaflet and its map plugins — BSD / MIT / ISC / Beerware
- Font Awesome Free icon styles (pulled in by Jupyter Widgets; font binaries are stripped at build time) — MIT / CC BY 4.0 / SIL OFL 1.1
All are permissively licensed and redistributable; attributions are collected in
NOTICE. Python runtime dependencies (tkwry, ipywidgets, comm,
traitlets, markdown) are installed by pip as normal and are not vendored.
📦 Installation
From PyPI (prebuilt front end included — no npm / Node required):
pip install tkipw
pip install "tkipw[demo]" # plotting, data, image, and 3D demos
From a source checkout (editable):
pip install -e .
pip install -e ".[demo]"
Rebuild the front end only when you change js/ (or after a fresh clone
before the first editable run, if runtime.js is not present yet):
cd js && npm install && npm run build
🚀 Usage
from tkipw import App, display
import matplotlib.pyplot as plt
app = App()
plt.plot([1, 2, 3], [1, 4, 9])
plt.show() # routed into the output area (viewer mode — default)
app.run()
Pop-up windows (%matplotlib tk style for figures, and for any display()):
from tkipw import App, display
app = App(title="host", display_mode="window")
display(some_chart) # opens a Tk pop-up (host root stays hidden)
app.run()
Interactive widgets work as usual:
from tkipw import App
import ipywidgets as widgets
app = App()
slider = widgets.IntSlider(description="n", value=10)
app.display(slider)
app.run()
Embed in an existing Tk layout with WidgetFrame (a tk.Frame that you pack
yourself):
import tkinter as tk
import ipywidgets as widgets
from tkipw import WidgetFrame
root = tk.Tk()
view = WidgetFrame(root)
view.pack(fill="both", expand=True)
view.display(widgets.IntSlider())
root.mainloop()
import ipywidgets / import anywidget work unchanged.
app.display(...)/view.display(...)— mount widgets in this host's WebViewdisplay/clear_output/Output— notebook-style output under the celldisplay(..., display_id=True)— returns a handle;handle.update(...)replaces that outputregister_mime_renderer(mime, fn)— extra_repr_mimebundle_types → HTMLregister_widget_module(name, path)— load a classic AMD/nbextension JS module from a local file or directory (not bundled, not CDN).App()also discovers modules already installed under JupyternbextensionsApp(display_mode="viewer"|"window")— output pane vs one Tk pop-up perdisplay()(window mode hides the host root so only the pop-ups are visible)."inline"is a deprecated alias for"viewer"plt.show()— follows the active App (PNG in the viewer pane, or native TkAgg windows)
Import order:
from tkipw import Appbefore you create widgets, so they bind to tkipw's Comm backend instead of aDummyComm.
🔄 Multiple Apps & cleanup
Several Apps can be alive at once. The most recently used one (the one you
last called display() / activate() on) receives newly created widget comms.
destroy() cleans up that App, and when the last App closes, tkipw restores
the process-wide patches it installed (Comm backend registry, IPython display
bridge, logging handler, sys.excepthook).
a = App(title="A")
b = App(title="B")
a.display(widgets.Button(description="in A")) # activates A → renders in A
b.display(widgets.Button(description="in B")) # activates B → renders in B
with a.activate():
widgets.IntSlider() # new comms go to A
# B is active again
a.destroy()
b.destroy() # last one out tears down global patches
The monkey-patches are also individually reversible:
uninstall_comm_backend(), uninstall_jupyter_support().
📁 Examples
pip install "tkipw[demo]" # or: pip install -e ".[demo]"
python examples/playground.py # viewer: Monaco editor + stacked output
python examples/plotly_demo.py # window: Plotly FigureWidget pop-up
python examples/ipyleaflet_demo.py # window: live ipyleaflet map pop-up
python examples/ipycanvas_demo.py # window: live ipycanvas Canvas pop-up
python examples/bqplot_demo.py # window: live bqplot Figure pop-up
python examples/ipympl_demo.py # window: interactive Matplotlib (ipympl)
python examples/bokeh_demo.py # window: Bokeh ``show(plot)`` pop-up
python examples/altair_demo.py # window: Altair ``display(chart)`` pop-up
python examples/pillow_demo.py # window: Pillow ``Image.show()`` pop-up
| Script | Mode | Description |
|---|---|---|
examples/playground.py |
viewer | Monaco multi-tab editor + stacked live output |
examples/plotly_demo.py |
window | Plotly FigureWidget in a Tk pop-up |
examples/ipyleaflet_demo.py |
window | Live ipyleaflet widget map in a Tk pop-up |
examples/ipycanvas_demo.py |
window | Live ipycanvas Canvas in a Tk pop-up |
examples/bqplot_demo.py |
window | Live bqplot Figure in a Tk pop-up |
examples/ipympl_demo.py |
window | Interactive Matplotlib (ipympl) in a Tk pop-up |
examples/bokeh_demo.py |
window | Bokeh show(plot) in a Tk pop-up |
examples/altair_demo.py |
window | Altair display(chart) in a Tk pop-up |
examples/pillow_demo.py |
window | Pillow Image.show() in a Tk pop-up |
🖥️ Playground
A viewer-mode IDE-like playground with a Monaco multi-tab editor on the left and stacked notebook-style output on the right:
python examples/playground.py
Samples (README.md / matplotlib / ipympl / pyvista / pandas / Folium / ipyleaflet / ipycanvas / bqplot / …) open as
tabs. Running a .md or .markdown tab renders the file directly in the
themed output pane; Python code can render the same content with
IPython.display.Markdown. Run the active tab with the Run button or
⌘/Ctrl+Enter. While Python is running, the green play button becomes a red stop
button; stopping cooperative Python execution reports the interruption in the
output pane. The menu bar has
New/Open/Save, Undo/Redo, Find/Replace, Minimap, Word Wrap, editor theme, and a
View → Display Mode → Viewer / Window selector. Viewer results are stacked
in the toggleable output pane; Window mode opens each display() in a separate
Tk pop-up. Monaco loads from a CDN on first run.
🧩 Jupyter extensions
IPython.display.display(), tkipw.display() and App.display() all go through
one transform gateway, so library-specific display fixes live in extensions:
from tkipw import register_extension
class MyExtension:
name = "my-library"
def setup(self):
... # initialise as a notebook front end
def transform(self, obj):
return obj # adapt for the WebView if needed
register_extension(MyExtension())
Extra _repr_mimebundle_ keys (not a whole library) go through
register_mime_renderer(mime, fn) instead. Classic AMD widget JS (for example
ipydatagrid) is picked up from Jupyter nbextensions when you create an
App, or loaded with register_widget_module(name, path) — not from a CDN.
Built-ins:
- Matplotlib — follows the active App's
display_modeby default:viewer→ PNG in the output area;window→ native TkAgg figure windows (%matplotlib tkstyle).import matplotlibalone keeps that path.import ipymplswitches to interactive WebView canvases (%matplotlib widget); Appdisplay_modestill chooses the viewer pane vs pop-up. The Playground resets the backend from each tab's source so a matplotlib-only tab does not stay stuck on ipympl after an earlier run. Shortcuts:matplotlib_inline()/matplotlib_window()/matplotlib_widget(). - Folium — pixel
Map(width=…, height=…)becomes a fixed-size hosted map (preferred in window mode). Percentage sizes keep the notebook HTML. - ipyleaflet — bundled
jupyter-leafletmodule renders live widget maps; map/layer trait changes continue to flow over the tkipw Comm bridge. - ipycanvas — bundled
ipycanvasmodule renders live Canvas widgets; drawing commands and pointer events flow over the Comm bridge. - bqplot — bundled
bqplot+bqscalesmodules render interactive SVG figures; marks/scales/axes sync over the Comm bridge. Toolbar Save (data:/blob:) opens a native file dialog; HTTP(S) files use tkwryon_download. - ipympl — bundled
jupyter-matplotlibmodule for interactive Matplotlib zoom/pan toolbars in the WebView (activated byimport ipympl). - Pillow —
Image.show()→ PNG viadisplay()(viewer pane or pop-up) - Altair — standalone Vega-Lite HTML hosted in a responsive iframe
- Bokeh —
show()/ displayed models → standalone HTML hosted in an iframe - PyVista —
handle_plotter → show_trame → IPython.display. On macOS thetrame/serverbackends are remapped toclient, because native VTK OpenGL + WKWebView crash (SIGTRAP). Large offline-htmlsrcdociframes are served over a loopbackLocalHTMLHostfor WebView compatibility.
🏗️ Architecture
- Python —
comm.create_comm→TkwryComm; official ipywidgets messages sent as JSON (+base64 buffers) - JS —
@jupyter-widgets/html-manager+window.ipcinruntime.js; leaflet, ipycanvas, bqplot/bqscales, and jupyter-matplotlib load as packs on first use - Bridge — a stack of active
Apps; the top receives new comm traffic - Navigation — the widget shell stays on loopback; other loopback ports
(PyVista trame) stay in the WebView; public http(s) links open in the
system browser (tkwry
on_navigation/open_external)
🧪 Tests
python -m tkipw doctor # packages, WebView engine, bundled JS runtime
pytest -m "not e2e" # fast, display-free unit tests
TKIPW_E2E=1 pytest -m e2e # real WebView: boot, comm, and extension DOM regression
CI runs the unit tests on Windows / macOS / Linux, plus the WebView E2E suite
on Linux (Xvfb) and macOS (runtime/comm and extension display paths, split to
avoid WebKitGTK hangs). See .github/workflows/ci.yml.
⚠️ Known limitations
- Alpha — APIs may change
- Widget coverage — standard ipywidgets controls + anywidget + ipyleaflet
- ipycanvas + bqplot + ipympl are bundled. Other classic AMD widgets are
discovered from Jupyter
nbextensions(orregister_widget_module). JupyterLab Module Federation is out of scope.
- ipycanvas + bqplot + ipympl are bundled. Other classic AMD widgets are
discovered from Jupyter
- PyVista on macOS — client-side rendering only (see extensions above)
- External links — http(s) outside loopback open in the system browser, not inside the widget WebView
- Platform behavior — inherits tkwry's platform notes (macOS embedding, import order, Linux source build)
📝 License
MIT. The bundled JavaScript embeds third-party libraries (Jupyter Widgets and
Lumino under BSD-3-Clause; anywidget, jQuery, and Backbone under MIT; Font
Awesome Free icon styles under MIT / CC BY 4.0 / OFL 1.1) — all permissive and
redistributable. See NOTICE for full attributions.
Built on tkwry.
See CHANGELOG.md for release history.
👨💻 Author
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tkipw-0.0.4.tar.gz.
File metadata
- Download URL: tkipw-0.0.4.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54c607727f2d8ebfcabce81cf67945d0db61d7c859c722ba6678aca3955d6733
|
|
| MD5 |
5208b718b90526f4577acaf1f3e4a256
|
|
| BLAKE2b-256 |
c4faa0018a16502a4d411ac91af2efb9f1c1cc588a81a9013f0f289a5a515768
|
Provenance
The following attestation bundles were made for tkipw-0.0.4.tar.gz:
Publisher:
release.yml on mashu3/tkipw
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tkipw-0.0.4.tar.gz -
Subject digest:
54c607727f2d8ebfcabce81cf67945d0db61d7c859c722ba6678aca3955d6733 - Sigstore transparency entry: 2489591758
- Sigstore integration time:
-
Permalink:
mashu3/tkipw@8fdc5d7921a0e7ef3c1c285b20869001a8e52da8 -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/mashu3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8fdc5d7921a0e7ef3c1c285b20869001a8e52da8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tkipw-0.0.4-py3-none-any.whl.
File metadata
- Download URL: tkipw-0.0.4-py3-none-any.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d01c72b84ae771c1687cdc321731dfdef614c34c309297c5547db45ee2cfd484
|
|
| MD5 |
85ad46bb917fa859684a043e3c4daf07
|
|
| BLAKE2b-256 |
7292177f966b9ff95b76eec0c63ca6fb3057553301fbfcf2bbec03bf32fe5c1a
|
Provenance
The following attestation bundles were made for tkipw-0.0.4-py3-none-any.whl:
Publisher:
release.yml on mashu3/tkipw
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tkipw-0.0.4-py3-none-any.whl -
Subject digest:
d01c72b84ae771c1687cdc321731dfdef614c34c309297c5547db45ee2cfd484 - Sigstore transparency entry: 2489592007
- Sigstore integration time:
-
Permalink:
mashu3/tkipw@8fdc5d7921a0e7ef3c1c285b20869001a8e52da8 -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/mashu3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8fdc5d7921a0e7ef3c1c285b20869001a8e52da8 -
Trigger Event:
push
-
Statement type: