Skip to main content

tkipw

License: MIT PyPI - Python Version GitHub Release PyPI Version Downloads Status: Alpha CI

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-manager runs 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, and logging all show up in an output area
  • One event loop — everything runs on Tk's mainloop

🔧 Requirements

  • Python 3.10+
  • tkwry 0.1.8+ (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 WebView
  • display / clear_output / Output — notebook-style output under the cell
  • display(..., display_id=True) — returns a handle; handle.update(...) replaces that output
  • register_mime_renderer(mime, fn) — extra _repr_mimebundle_ types → HTML
  • register_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 Jupyter nbextensions
  • App(display_mode="viewer"|"window") — output pane vs one Tk pop-up per display() (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 App before you create widgets, so they bind to tkipw's Comm backend instead of a DummyComm.

🔄 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_mode by default: viewer → PNG in the output area; window → native TkAgg figure windows (%matplotlib tk style). import matplotlib alone keeps that path. import ipympl switches to interactive WebView canvases (%matplotlib widget); App display_mode still 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-leaflet module renders live widget maps; map/layer trait changes continue to flow over the tkipw Comm bridge.
  • ipycanvas — bundled ipycanvas module renders live Canvas widgets; drawing commands and pointer events flow over the Comm bridge.
  • bqplot — bundled bqplot + bqscales modules 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 tkwry on_download.
  • ipympl — bundled jupyter-matplotlib module for interactive Matplotlib zoom/pan toolbars in the WebView (activated by import ipympl).
  • Pillow — Image.show() → PNG via display() (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 the trame / server backends are remapped to client, because native VTK OpenGL + WKWebView crash (SIGTRAP). Large offline-html srcdoc iframes are served over a loopback LocalHTMLHost for WebView compatibility.

🏗️ Architecture

  • Python — comm.create_comm → TkwryComm; official ipywidgets messages sent as JSON (+base64 buffers)
  • JS — @jupyter-widgets/html-manager + window.ipc in runtime.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 (or register_widget_module). JupyterLab Module Federation is out of scope.
  • 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

mashu3

Contributors

Metadata

Release files for tkipw 0.0.5

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

Source distribution (sdist)

Source distribution for tkipw 0.0.5
File Size Uploaded
tkipw-0.0.5.tar.gz 1.5 MB Details

Built distribution (wheel)

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

Total release size: 2.9 MB

Release files / tkipw-0.0.5.tar.gz

Download URL tkipw-0.0.5.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
1b16b0e6c900b35411df9a6929fab2c43752e94ad140623c6def6891a901f9df
BLAKE2b-256 checksum
How to use checksums
5c8e5796bd5a68996705dae89bc1b3fd666f1fe62794fbfab8ae6542bf8d17fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / tkipw-0.0.5-py3-none-any.whl

Download URL tkipw-0.0.5-py3-none-any.whl
Size 1.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
324b67bbbdd0f376b0c30d952712ba95d533cbdab182a6e9de3798f4544298d3
BLAKE2b-256 checksum
How to use checksums
bcbf8999e76f9b4add94f62554dccebc35daf28b44b44e180f2af1030cb5aadd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.5 This release

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

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