Skip to content

Repository files navigation

tk-uia

tests License: MIT Ruff

Make Tkinter applications fully accessible to Windows screen readers and UI Automation: named, correctly typed, and activatable.

Tk 8.6 exposes widgets to Windows accessibility with no names and mostly wrong control types: buttons are unnamed, labels read as images, and every themed ttk widget is an anonymous pane. tk_uia.enable(root) fixes both halves. Each widget is annotated through MSAA for legacy clients, and answers UI Automation for itself: Invoke genuinely presses, Value genuinely types, Toggle, SelectionItem and RangeValue genuinely act, and names and states are read live from the widget at the moment a client asks. Screen readers such as NVDA, and UIA tools such as Inspect.exe or pytest-uia, see controls they can both read and operate. No runtime dependencies, no C extension, no visible change to the window.

Install

pip install tk-uia

Windows only. On other platforms enable() returns UNSUPPORTED and does nothing, so cross-platform code can call it unconditionally.

Usage

import tkinter as tk
import tk_uia

root = tk.Tk()
root.title("Tasks")
tk.Label(root, text="Task list").pack()
tk.Button(root, text="New Task", command=create).pack()

tk_uia.enable(root)
root.mainloop()

enable() comes first: every other call (set_acc_name, label_for, the bind_* family) refuses until it has run.

Widget Bare Tk After enable()
tk.Button(text="New Task") ButtonControl, no name, Invoke does nothing ButtonControl, Name='New Task', Invoke presses it
tk.Label(text="Task list") ImageControl, no name TextControl, name read live
tk.Checkbutton(text="Done") ButtonControl, no name CheckBoxControl, Toggle works, live ToggleState
tk.Entry(textvariable=var) PaneControl, no ValuePattern EditControl, SetValue types into it
ttk.Button(text="Save") anonymous PaneControl ButtonControl, named, Invoke presses it
tk.Scale(from_=0, to=10) PaneControl SliderControl, RangeValue moves it

Measured from a separate process. COVERAGE.md has the table for every widget class in both toolkits. COOKBOOK.md builds a real form end to end.

Features

  • One call covers the whole application, including windows opened later.
  • Working patterns per class: Invoke for buttons, Toggle for checkbuttons, SelectionItem for radiobuttons and notebook tabs, Value for entries, spinboxes, comboboxes and Text, RangeValue for scales and progressbars.
  • Names, values, enabled state, help and description are pulled live at the moment a client asks; name and value changes are raised as UIA property-changed events.
  • Names inferred from -text, correct control types for classic tk and ttk.
  • A declared textvariable is followed automatically: the name or value stays current with no further code.
  • label_for(label, entry) records which caption names which field. infer_names_from_layout(root) retrofits an existing dialog in one call and reports every name it chose.
  • describe(root) returns an audit you can print(): what a client gets and what is missing, with a reason and a fix per widget. Usable as data for CI gating.
  • Listbox rows and Treeview items are real elements: named, nested under their branches, selected for real (the application hears its own <<ListboxSelect>>/<<TreeviewSelect>>), joined to and removed from a selection where selectmode takes more than one, scrolled into view through ScrollItem, and a tree branch opens through ExpandCollapse. Selection changes are raised as UIA events whenever the widget's own select event fires: a user's choice, a client's, and any treeview change.
  • Notebook tabs become real tab controls a client can switch without a click.
  • annotate_only(root) keeps the previous annotation-only behaviour, and leave_to_the_proxy(widget) opts a single widget out.
  • Detects Tk 9.1's native accessibility (TIP 733) and stands down.

Limitations

  • In-process only. You can make your own application accessible, not someone else's.
  • On a single-select container (selectmode browse or single), AddToSelection and RemoveFromSelection are refused; Select is the one selection there is.
  • A combobox has no ExpandCollapse yet, so a client cannot open its dropdown; choosing goes through ValuePattern.SetValue, which on a readonly combobox takes exactly the values a user could pick and fires the same <<ComboboxSelected>> a dropdown choice would.
  • infer_names_from_layout is a guess by convention: rows are frames, or grid rows within a frame, read across their columns. It returns every name it chose; read the guess before shipping it.
  • A widget left to the MSAA proxy advertises an InvokePattern that does nothing; that is the proxy's own behaviour, and describe() says which widgets it applies to.
  • Verified against the UI Automation tree, which is what screen readers consume. Not yet verified against NVDA speech output; that is what 1.0 means on the roadmap.

Documentation

COOKBOOK.md Your first accessible form, in ten minutes.
docs/GUIDE.md Full API, how it works, every caveat and measurement.
COVERAGE.md Every widget class, measured bare and after enable().
ROADMAP.md What is next and what is out of scope.
CHANGELOG.md Release history.

License

MIT

About

Make Tkinter applications fully accessible to Windows screen readers and UI Automation: named, correctly typed, and activatable. One call, zero runtime dependencies.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages