Skip to main content

A declarative approach to building Tkinter interfaces.

Project description

declarative-tkinter

A small declarative UI layer on top of Python's Tkinter — inspired by SwiftUI-style composition.

This project is Alpha. APIs may change.

Note: this document is written by AI and may contain inaccuracies, because this library origianlly developed as a part of my homework for the academic source.

Features

  • Compose UI as a tree (VStack, HStack, Grid, TabView, ...)
  • Chainable styling and event handlers (padding, fill, onClicked, ...)
  • App/window primitives (App, Window) and a simple menu API (Menu)

Requirements

  • Python >= 3.14
  • Tk >= 9.0
  • Dependency: Pillow>=12.0.0

Note: importing declarative_tkinter performs runtime checks for Python/Tk versions and raises RuntimeError when the requirements are not met.

Installation

pip install declarative-tkinter

Editable install for development:

pip install -e .

Quick start

Create an App, create a Window, lay out a component tree into window.window, then call app.ready().

from declarative_tkinter import App, Window
from declarative_tkinter.component import VStack, Text, Button


def main() -> None:
	app = App()
	window = Window(app, width=360, height=180)
	window.set_title("declarative-tkinter")

	ui = VStack(
		Text("Hello, Tkinter!"),
		Button(text="Quit").onActive(lambda _btn: app.quit()),
	).padding(12)

	ui.layout(window.window)
	app.ready()


if __name__ == "__main__":
	main()

Components (excerpt)

Most components live in declarative_tkinter.component.

  • Layout: VStack, HStack, Grid, GridRow, VSplitView, HSplitView, TabView
  • Display: Text, Image, ImagePNG, ImageSVG, Separator, Rectangle
  • Input: TextField, SecureField, TextEditor, Stepper, CheckBox
  • Other: Canvas

Common chainable helpers include:

  • padding(...), fill("x"|"y"|"both", expand=...)
  • foregroundColor(...), backgroundColor(...)
  • borderWidth(...), focusBorder(...)
  • frame(width=..., height=...), font(...), alignment(...), justify(...)
  • onClicked(...), onMouseEnter(...), onMouseLeave(...)

TextField example

TextField uses a tkinter.StringVar and supports onChange / onSubmit callbacks.

import tkinter

from declarative_tkinter import App, Window
from declarative_tkinter.component import VStack, Text, TextField, Button


def main() -> None:
	app = App()
	window = Window(app, width=420, height=220)
	window.set_title("TextField demo")

	name = tkinter.StringVar(value="")
	message = Text("(empty)")

	ui = VStack(
		TextField(name, placeholder="Your name")
			.onSubmit(lambda _tf, var: message.setText(f"Hello, {var.get()}!")),
		message,
		Button(text="Quit").onActive(lambda _btn: app.quit()),
	).padding(12)

	ui.layout(window.window)
	app.ready()


if __name__ == "__main__":
	main()

Real-world layout example (Grapher-like editor shell)

When your UI looks like an “app shell” (toolbar + sidebars + a main canvas + a status bar), plain Tkinter tends to become a long sequence of Frame(...), pack(...), and widget wiring.

With declarative-tkinter, you can describe the layout as a single tree and attach behavior where it belongs:

import tkinter
import tkinter.font

from declarative_tkinter import App, Window
from declarative_tkinter.component import VStack, HStack, Text, TextField, Canvas, Separator


def main() -> None:
	app = App()
	window = Window(app, width=1000, height=700)
	window.set_title("Editor shell demo")

	command = tkinter.StringVar(value="")
	status = Text("Ready").name("status")

	body = VStack(
		# Top command bar
		HStack(
			Text("Command").padding(horizontal=8),
			TextField(command, placeholder="Type a command and press Enter")
				.padding(horizontal=8, vertical=4)
				.focusBorder(backColor="#717171", focusColor="#6889f6", width=2)
				.font(tkinter.font.Font(family=("Menlo"), size=10))
				.fill("x", expand=True)
				.onSubmit(lambda _tf, var: status.setText(f"Executed: {var.get().strip()}")),
		).frame(height=30).fill("x").backgroundColor("#2C2C2C"),

		# Main area
		HStack(
			# Left toolbar
			VStack(
				Text("Move").padding(6).pointer("pointinghand")
					.onClicked(lambda _ui, _ev: status.setText("Mode: move")),
				Text("Select").padding(6).pointer("pointinghand")
					.onClicked(lambda _ui, _ev: status.setText("Mode: select")),
				Separator().padding(vertical=10),
			).frame(width=80).fill("y").backgroundColor("#3B3B3B"),

			# Center canvas (scrollable)
			VStack(
				Canvas(1, 1, scrollable=True)
					.fill("both", expand=True)
					.backgroundColor("#ffffff")
					.name("main_canvas"),
			).fill("both", expand=True).backgroundColor("#202020"),

			# Right sidebar (inspector)
			VStack(
				Text("Inspector").padding(8),
			).frame(width=260).fill("y").backgroundColor("#232323"),
		).fill("both", expand=True),

		# Status bar
		HStack(
			status.padding(vertical=6, horizontal=12),
		).frame(height=30).fill("x").backgroundColor("#0B3881"),
	).backgroundColor("#1E1E1E").fill("both", expand=True)

	body.layout(window.window)

	# Optional: access widgets later via name lookup
	canvas_item = body.getUIByName("main_canvas")
	if isinstance(canvas_item, Canvas) and isinstance(canvas_item.widget, tkinter.Canvas):
		canvas_item.widget.create_text(20, 20, text="Hello Canvas", anchor="nw")

	app.ready()


if __name__ == "__main__":
	main()

Menu

Attach a menu to a window using Window.set_menu().

from declarative_tkinter import (
	App,
	Window,
	Menu,
	MenuSubmenu,
	MenuButton,
	MenuSeparator,
	Notification,
)


def main() -> None:
	app = App()
	window = Window(app, width=320, height=160)
	window.set_title("Menu demo")

	window.set_menu(
		Menu([
			MenuSubmenu(
				"File",
				items=[
					MenuButton(
						"Notify",
						click=lambda app: app.notify(Notification("declarative-tkinter", "Hello from Menu")),
					),
					MenuSeparator(),
					MenuButton("Quit", click=lambda app: app.quit(), accelerator="Cmd+Q"),
				],
			),
		])
	)

	app.ready()


if __name__ == "__main__":
	main()

Platform notes

  • App.notify(...) calls Tk's sysnotify. It may fail depending on platform configuration and will return False.
  • Some Window APIs (e.g. is_dark_mode, set_bounce) are macOS-only.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

declarative_tkinter-0.1.2.tar.gz (33.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

declarative_tkinter-0.1.2-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file declarative_tkinter-0.1.2.tar.gz.

File metadata

  • Download URL: declarative_tkinter-0.1.2.tar.gz
  • Upload date:
  • Size: 33.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for declarative_tkinter-0.1.2.tar.gz
Algorithm Hash digest
SHA256 f2dc999b59fe68c2f0c012270be8d628830409e3f3cd6f4fc6bd4bbe9a51c191
MD5 56372a5fa6fe6cd5f54400330548d016
BLAKE2b-256 06757b292832b3ecf44c667470dfd6f9ed5a905c53ad8d0536832f2aa3d2a86c

See more details on using hashes here.

File details

Details for the file declarative_tkinter-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for declarative_tkinter-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a65b9521567ba98f561c3be092c7f76b4b8375a48edb0607f6a026f8b93aef3d
MD5 d374fb367cf69de9b32591e5ce563461
BLAKE2b-256 5c088644a4359735e79f214746b84564e05a7477bc8cf86356661a3ce6d107be

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page