Keep Tkinter — give it the WebView it never had.
Embed a real system WebView (wry) inside your Frame: modern HTML, JS, and IPC in the same layout as your buttons and tabs — one mainloop, no floating overlay.
Alpha — Early preview (see PyPI badge for the current version). APIs and behavior may change without notice. Not recommended for production use yet. Prefer the Python
WebView/WebSessionAPI;tkwry._coreand.nativeare Internal (no SemVer) — see Usage — API stability.
Tkinter is still a solid GUI shell — it just had no first-class way to host modern web content inside a widget. Overlay-style WebViews drift out of sync when you move, resize, or switch tabs.
tkwry fills that missing piece:
- True child embedding —
build_as_childvia HWND, NSView, or X11 window ID - One event loop — Tk
mainlooponly; no separate app runtime - Local apps —
app=serves HTML/CSS/JS without a localhost HTTP server (tkwry://on macOS/Linux; Windows defaults tohttps://tkwry.localhost) - IPC / RPC / emit — JS↔Python events, request/response, and streams without freezing the UI
- Trust boundaries — IPC/RPC default to the initial origin;
untrusted=Truefor arbitrary sites - Layout-aware — tracks
pack/grid/place, tabs, andPanedWindow
Tkinter apps already have a window and a layout. The web belongs inside a Frame — same mainloop, same tabs and panes — not in a separate top-level webview that floats beside your UI. tkwry wraps wry's build_as_child against the native surface Tk gives your widgets.
Pre-built abi3 wheels: Windows and macOS. Linux is source-only (best-effort by design).
| OS | Arch | Parent handle | Engine |
|---|---|---|---|
| Windows | x86_64, arm64 | Frame.winfo_id() → HWND |
WebView2 |
| macOS | arm64, x86_64 | Toplevel content NSView |
WKWebView |
| Linux | — | winfo_id() → X11 window ID |
WebKitGTK |
DPI, WebView2, macOS embedding / import order, and Linux eval caveats: Platform notes.
- Python 3.10+
- Tkinter (included with most Python builds)
- Building from source (git clone,
pip install git+…, or Linux) — Rust toolchain (stable);pipuses maturin as the build backend - Windows (x86_64, arm64) — WebView2 Runtime (no fallback engine; see Platform notes)
- macOS — 11 (Big Sur)+, arm64 or x86_64; system WKWebView
- Linux — WebKitGTK 4.1 + GTK 3; X11 or XWayland (
$DISPLAY); source build only (see Installation and Platform notes)
pip install tkwryCloning the repo and installing locally compiles the Rust extension on your machine. You need a Rust toolchain (rustup) and platform runtimes from Requirements above (WebView2 on Windows, etc.). pip pulls in maturin automatically as the build backend.
git clone https://github.com/mashu3/tkwry.git
cd tkwry
pip install -e .Use this for development and for running the examples from the tree.
pip install git+https://github.com/mashu3/tkwry.gitThis builds from source (sdist via git), not a pre-built wheel — needs Rust, same as pip install .. Prefer the PyPI wheel on Windows and macOS unless you need unreleased commits.
Install system dependencies, then build from source (support posture: Platform notes):
# Debian / Ubuntu
sudo apt install \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libglib2.0-dev
# Runtime (for end users of your app)
# sudo apt install libwebkit2gtk-4.1-0 libgtk-3-0
pip install maturin
git clone https://github.com/mashu3/tkwry.git
cd tkwry
pip install .GTK events are pumped automatically on a Tk timer while your app runs.
import tkinter as tk
from tkwry import WebView
root = tk.Tk()
root.geometry("900x600")
frame = tk.Frame(root, bg="#222")
frame.pack(fill="both", expand=True, padx=8, pady=8)
web = WebView(frame, url="https://github.com")
web.when_failed(lambda exc: print("native create failed:", exc))
root.mainloop()The constructor does not raise if native create fails. Handle
when_failed / <<WebViewCreateFailed>>. Minimal app, app=, hidden hosts,
User-Agent, downloads, cleanup, and the API table:
Usage
(Minimal app).
IPC / RPC / stream: docs/rpc.md.
Trust (untrusted, bridge_origins): docs/trust.md.
Embedding & layout
- Native child of your Tk surface (
build_as_child) — not a floating overlay - Bounds / visibility follow
<Configure>,<Map>,<Unmap>(Notebook tabs hide unmapped views) - Works with
pack/grid/place,Notebook, andPanedWindow; window chrome is the host Toplevel (Layout / resize)
Local apps & bridge
app=serves assets without a localhost HTTP server (tkwry://on macOS/Linux; Windows defaults tohttps://tkwry.localhost)- IPC / RPC / emit between JS and Python (docs/rpc.md)
- Origin-scoped bridge by default;
untrusted=for arbitrary sites (docs/trust.md)
Browser-ish APIs
WebSession/ profiles, cookies (Usage — Shared session); navigation hooks, downloads, print, DevTools (platforms)- Native file drag & drop into the WebView area (notify-only)
Host integration
- Typed create / eval / navigation / download failure signals on the Tk thread
- Lifecycle callbacks deferred onto Tk (avoids native-thread deadlocks)
tkwry.testingwait helpers for integration tests
Plotly / Folium / Markdown demos live under Examples. Prefer
tkwry_browser.py as the full-layout sample
(docs/examples-browser.md).
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .The easiest way to see tkwry in a full layout: toolbar + side pane + content tabs, all as child WebViews.
| macOS · dark | Windows · light |
|---|---|
| Script | Description |
|---|---|
examples/tkwry_browser.py |
Flagship mini-browser (single file): app= toolbar / side / Settings, separate content WebSession, New Tab start page, profiles, shortcuts |
python examples/tkwry_browser.py
python examples/tkwry_browser.py --privateArchitecture, trust split, and what to copy: docs/examples-browser.md.
| Script | Description |
|---|---|
examples/ipc_demo.py |
IPC events, RPC (call / kwargs / worker), stream (ticks + cancel), and emit |
examples/multi_demo.py |
Multiple WebViews, tabs, panes; emit_all flash |
examples/plotly_demo.py |
Plotly charts — CDN or local app= (pip install plotly) |
examples/folium_demo.py |
Folium maps (pip install folium; tiles need the network) |
examples/markdown_demo.py |
Monaco markdown editor + live preview (CDN) |
examples/dnd_demo.py |
Native file drag & drop into WebView |
python examples/ipc_demo.py
python examples/multi_demo.py
python examples/plotly_demo.py
python examples/folium_demo.py
python examples/markdown_demo.py
python examples/dnd_demo.pyRelated: Jupyter-style widgets (tkipw)
Built on tkwry. Use when you want the usual ipywidgets / anywidget stack in Tk (not plain HTML + JS):
| Script | Description |
|---|---|
plotly_demo.py |
Plotly FigureWidget |
ipyleaflet_demo.py |
Live ipyleaflet map |
See the tkipw examples for more.
Short checklist — details live in Platform notes (especially macOS embedding).
Platforms
- Windows — WebView2 Runtime required; missing → create-failed signals (install notes)
- Linux — no PyPI wheel (by design); best-effort source install; prefer sequential
eval_js_with_callbackacross multiple views (Linux)
Engine gaps / partial wraps (no invented shims)
- Print — system dialog (
print(); macOS alsoprint_with_optionsfor margins); no PDF / no result (Print) - Downloads — start-deny only; no mid-flight abort, pause/resume, or progress % (Downloads)
- Cookies / browsing data — Cookie CRUD +
clear_all_browsing_data()(WebView wipe-all); no dedicatedlocalStorage/ cache / IndexedDB Python APIs and no selective clear (Cookies / browsing data) - Screenshot / find in page — not exposed as tkwry APIs (Windows may still show engine Ctrl+F chrome) (Screenshot, Find)
macOS / Windows quirks
- macOS — import
tkwrybefore AppKit; inlineurl()may beNone; DevTools needsdevtools=Truethenopen_devtools()(private APIs — avoid Mac App Store) (macOS embedding, DevTools) - Windows DevTools —
open_devtools()works;close_devtoolsis a no-op;is_devtools_openalwaysFalse(DevTools)
Trust & session
- Shared
WebSession+app=must use the same root and matching serve options (includinghttps_scheme); do not share a persistent profile with untrusted sites - External content / IPC defaults and
untrusted=— Trust boundaries
Lifecycle & IPC
- Sync
on_navigation/on_new_window/on_download/ create-timepermission_handlermay block the engine until they return (wait capped ~60s); do not create a WebView fromon_new_window(lifecycle callbacks) - RPC cancel /
destroy()are cooperative only; async queues cap at 2048 each; IPC/RPC messages at 10 MiB (RPC limits, timeout/cancel) - Eval / navigation timeouts surface typed events/errors on the Tk thread (not raised on the WebKit thread)
- Native drag & drop is WebView area only and notify-only (cannot deny from Python; use tkinterdnd2 for arbitrary Tk widgets)
See CHANGELOG.md for release history.
| Topic | Doc |
|---|---|
Usage (minimal app, app=, hidden hosts, UA, API) |
docs/usage.md |
| Mini-browser example (flagship layout / sessions / trust) | docs/examples-browser.md |
Trust boundaries (untrusted, bridge_origins, recipes) |
docs/trust.md |
IPC / RPC / emit (expose, call / stream, cancel, limits) |
docs/rpc.md |
| Platform notes (Windows / macOS / Linux, print, window chrome) | docs/platforms.md |
| wry embedding / API ownership map | docs/wry-embedding.md |
Packaging (PyInstaller / Nuitka → .exe / .app) |
docs/packaging.md |
This project is licensed under the MIT License. See LICENSE.
This project links against wry, which is dual-licensed (Apache-2.0 or MIT). tkwry uses wry under MIT; see NOTICE for attribution.