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 university course.

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.3.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.3-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: declarative_tkinter-0.1.3.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.3.tar.gz
Algorithm Hash digest
SHA256 5b4113228ac172ad4ddce71a38b9acd1ba4d0e1a2dca6769231cf9ccc90b18a4
MD5 b7161e940f1e8e212dc8d166c2f9094a
BLAKE2b-256 5089cc9df250b823ce040c30f9ae8ed66c06944042cfe648909d4020577bfc2b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for declarative_tkinter-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ac40ef830bab698aec09e5793bd0ee1b8384aea6e91a678e26036a5df9c82108
MD5 c298dfba114203df892702020924e2e2
BLAKE2b-256 c73e90ce6147826bdaa8b27260b2810abe4b00c93b7728d9f7f9412c1758f78a

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