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.
pip install tk-uiaWindows only. On other platforms enable() returns UNSUPPORTED and does
nothing, so cross-platform code can call it unconditionally.
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.
- One call covers the whole application, including windows opened later.
- Working patterns per class:
Invokefor buttons,Togglefor checkbuttons,SelectionItemfor radiobuttons and notebook tabs,Valuefor entries, spinboxes, comboboxes andText,RangeValuefor 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 classictkandttk. - A declared
textvariableis 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 canprint(): 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 whereselectmodetakes more than one, scrolled into view throughScrollItem, and a tree branch opens throughExpandCollapse. 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, andleave_to_the_proxy(widget)opts a single widget out.- Detects Tk 9.1's native accessibility (TIP 733) and stands down.
- In-process only. You can make your own application accessible, not someone else's.
- On a single-select container (
selectmodebrowseorsingle),AddToSelectionandRemoveFromSelectionare refused;Selectis 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_layoutis 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
InvokePatternthat does nothing; that is the proxy's own behaviour, anddescribe()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.
| 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. |