]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-153550: Modernize the older prose sections of the tkinter documentation...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 15 Jul 2026 16:27:21 +0000 (18:27 +0200)
committerGitHub <noreply@github.com>
Wed, 15 Jul 2026 16:27:21 +0000 (16:27 +0000)
Refresh the narrative "Tkinter life preserver" and "Handy reference"
sections to match the overhauled reference (gh-86726 and gh-153549).

* Rework "The packer" into a general "Geometry management" section
  covering grid, pack and place, and cross-link it with the Grid, Pack
  and Place reference classes.
* Rework "The window manager" and "Coupling widget variables", replacing
  the dated App(Frame) examples with direct Tk() and ttk examples.
* Replace page-number citations to Ousterhout's book with links to the
  relevant Tk man pages.
* Modernize the tkinter.ttk front matter and its Tcl/Tk links, and add
  cross-references between the two documents.
* Reconcile the "Packer options" and "Tk option data types" lists with
  the reference, and fill in the empty Entry index list.
* Fix the malformed target-selection list in tkinter.dnd.
(cherry picked from commit 93beea7e5a3c5a03253e6812a06f2497505aeb30)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Doc/library/tkinter.dnd.rst
Doc/library/tkinter.rst
Doc/library/tkinter.ttk.rst

index ba267c0e3840fe269bc8eac01ad927c12319476a..adb9d839505b21e453f3c08180982204332e6c1d 100644 (file)
@@ -16,21 +16,22 @@ a single application, within the same window or between windows. To enable an
 object to be dragged, you must create an event binding for it that starts the
 drag-and-drop process. Typically, you bind a ButtonPress event to a callback
 function that you write (see :ref:`Bindings-and-Events`). The function should
-call :func:`dnd_start`, where 'source' is the object to be dragged, and 'event'
+call :func:`dnd_start`, where *source* is the object to be dragged, and *event*
 is the event that invoked the call (the argument to your callback function).
 
 Selection of a target object occurs as follows:
 
-#. Top-down search of area under mouse for target widget
+#. Top-down search of the area under the mouse for a target widget:
 
- * Target widget should have a callable *dnd_accept* attribute
- * If *dnd_accept* is not present or returns ``None``, search moves to parent widget
- * If no target widget is found, then the target object is ``None``
+   * the target widget should have a callable *dnd_accept* attribute;
+   * if *dnd_accept* is not present or returns ``None``,
+     the search moves to the parent widget;
+   * if no target widget is found, the target object is ``None``.
 
-2. Call to *<old_target>.dnd_leave(source, event)*
-#. Call to *<new_target>.dnd_enter(source, event)*
-#. Call to *<target>.dnd_commit(source, event)* to notify of drop
-#. Call to *<source>.dnd_end(target, event)* to signal end of drag-and-drop
+#. Call to ``<old_target>.dnd_leave(source, event)``.
+#. Call to ``<new_target>.dnd_enter(source, event)``.
+#. Call to ``<target>.dnd_commit(source, event)`` to notify of the drop.
+#. Call to ``<source>.dnd_end(target, event)`` to signal the end of drag-and-drop.
 
 
 .. class:: DndHandler(source, event)
index 268eeebcc06b4b44175e549f197e434a4940733e..542f0484ac9aed26963d7ff82b410088a4609604 100644 (file)
@@ -37,7 +37,8 @@ details that are unchanged.
 .. note::
 
    Tcl/Tk 8.5 (2007) introduced a modern set of themed user interface components
-   along with a new API to use them. Both old and new APIs are still available.
+   along with a new API to use them (see :mod:`tkinter.ttk`).
+   Both old and new APIs are still available.
    Most documentation you will find online still uses the old API and
    can be woefully outdated.
 
@@ -521,6 +522,17 @@ Use the config() method to update multiple attrs subsequent to object creation
 
       fred.config(fg="red", bg="blue")
 
+.. note::
+
+   The ``fg`` and ``bg`` options used here,
+   and other options that control a widget's appearance,
+   belong to the classic :mod:`!tkinter` widgets.
+   The themed :mod:`tkinter.ttk` widgets recommended in the introduction
+   do not accept them;
+   style a themed widget through the :class:`ttk.Style <tkinter.ttk.Style>`
+   class instead.
+   The three ways of setting an option shown above apply to both widget sets.
+
 For a complete explanation of a given option and its behavior, see the Tk man
 pages for the widget in question.
 
@@ -569,65 +581,84 @@ their values.  This is meant only as an example.
 
 
 .. _pack-the-packer:
+.. _tkinter-geometry-management:
+.. _the-packer:
+.. _packer-options:
 
-The packer
-^^^^^^^^^^
-
-.. index:: single: packing (widgets)
-
-The packer is one of Tk's geometry-management mechanisms.    Geometry managers
-are used to specify the relative positioning of widgets within their container.
-In contrast to the more cumbersome *placer* (which is
-used less commonly, and we do not cover here), the packer takes qualitative
-relationship specification - *above*, *to the left of*, *filling*, etc - and
-works everything out to determine the exact placement coordinates for you.
+Geometry management
+^^^^^^^^^^^^^^^^^^^
 
-The size of any container widget is determined by the size of the "content widgets"
-inside.  The packer is used to control where content widgets appear inside the
-container into which they are packed.  You can pack widgets into frames, and frames
-into other frames, in order to achieve the kind of layout you desire.
-Additionally, the arrangement is dynamically adjusted to accommodate incremental
-changes to the configuration, once it is packed.
+.. index::
+   single: geometry management (widgets)
+   single: packing (widgets)
+
+Creating a widget does not display it.
+A widget appears only after it has been handed to a *geometry manager*,
+which works out its size and position inside its container
+and keeps the layout up to date as the container is resized or its content changes.
+Forgetting to call a geometry manager is a common early mistake:
+the widget is created, but nothing shows up.
+
+Tk provides three geometry managers.
+Each is inherited by every widget, so any widget can be managed by any of them
+(but see the warning below about the incompatibility of grid and pack).
+The choice depends on the kind of layout you want.
+
+:meth:`grid <Grid.grid_configure>`
+   Arranges widgets in a two-dimensional table of rows and columns.
+   It is the most flexible manager and the one to reach for by default:
+   layouts that would otherwise need several nested frames can often be
+   expressed as a single grid,
+   and rows and columns can be told how to absorb extra space.
 
-Note that widgets do not appear until they have had their geometry specified
-with a geometry manager.
-It's a common early mistake to leave out the geometry specification, and then
-be surprised when the widget is created but nothing appears.
-A widget will appear only after it has had, for example, the packer's
-:meth:`~Pack.pack` method applied to it.
+   ::
 
-The pack() method can be called with keyword-option/value pairs that control
-where the widget is to appear within its container, and how it is to behave when
-the main application window is resized.  Here are some examples::
+      ttk.Label(frm, text="Name:").grid(column=0, row=0, sticky="w")
+      ttk.Entry(frm).grid(column=1, row=0)
+      ttk.Button(frm, text="OK").grid(column=1, row=1, sticky="e")
 
-   fred.pack()                     # defaults to side = "top"
-   fred.pack(side="left")
-   fred.pack(expand=1)
+:meth:`pack <Pack.pack_configure>`
+   Stacks widgets against one side of their container
+   -- ``"top"`` (the default), ``"bottom"``, ``"left"`` or ``"right"`` --
+   and can make them fill or expand into the space that is left.
+   It is convenient for simple arrangements,
+   such as a single row or column of widgets
+   or a content area framed by a toolbar and a status bar.
 
+   ::
 
-Packer options
-^^^^^^^^^^^^^^
+      toolbar.pack(side="top", fill="x")
+      status.pack(side="bottom", fill="x")
+      body.pack(side="left", expand=True, fill="both")
 
-For more extensive information on the packer and the options that it can take,
-see the man pages and page 183 of John Ousterhout's book.
+:meth:`place <Place.place_configure>`
+   Positions each widget at an explicit spot,
+   given either as absolute screen distances or as a fraction of the container's size.
+   It offers the most control but the least automatic behavior, and is used the least;
+   it suits special cases such as overlapping widgets or precise custom layouts.
 
-anchor
-   Anchor type.  Denotes where the packer is to place each content in its parcel.
+   ::
 
-expand
-   boolean, ``0`` or ``1``.
+      background.place(x=0, y=0, relwidth=1.0, relheight=1.0)
+      badge.place(relx=1.0, rely=0.0, anchor="ne")
 
-fill
-   Legal values: ``'x'``, ``'y'``, ``'both'``, ``'none'``.
+Layouts are built up by nesting:
+grid or pack widgets, including frames, inside a frame or toplevel.
+Toplevels are managed by the OS window manager.
+Classic and themed :mod:`tkinter.ttk` widgets can be managed interchangeably.
 
-ipadx and ipady
-   A distance - designating internal padding on each side of the content.
+.. warning::
 
-padx and pady
-   A distance - designating external padding on each side of the content.
+   Do not apply :meth:`!pack` and :meth:`!grid` to two widgets that share the same container.
+   The two managers negotiate sizes in incompatible ways,
+   and the application can hang as they repeatedly resize the container against each other.
+   To combine them, keep each manager's widgets in a separate frame.
 
-side
-   Legal values are: ``'left'``, ``'right'``, ``'top'``, ``'bottom'``.
+The full set of options accepted by each manager, with their values and defaults,
+is documented under :meth:`Grid.grid_configure`, :meth:`Pack.pack_configure`
+and :meth:`Place.place_configure`;
+see also the :manpage:`grid(3tk)`, :manpage:`pack(3tk)` and :manpage:`place(3tk)`
+man pages.
 
 
 .. _coupling-widget-variables:
@@ -635,105 +666,98 @@ side
 Coupling widget variables
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The current-value setting of some widgets (like text entry widgets) can be
-connected directly to application variables by using special options.  These
-options are ``variable``, ``textvariable``, ``onvalue``, ``offvalue``, and
-``value``.  This connection works both ways: if the variable changes for any
-reason, the widget it's connected to will be updated to reflect the new value.
-
-Unfortunately, in the current implementation of :mod:`!tkinter` it is not
-possible to hand over an arbitrary Python variable to a widget through a
-``variable`` or ``textvariable`` option.  The only kinds of variables for which
-this works are variables that are subclassed from a class called Variable,
-defined in :mod:`!tkinter`.
-
-There are many useful subclasses of Variable already defined:
-:class:`StringVar`, :class:`IntVar`, :class:`DoubleVar`, and
-:class:`BooleanVar`.
-To read the current value of such a variable, call the :meth:`~Variable.get`
-method on it, and to change its value you call the :meth:`!set` method.
-If you follow this protocol, the widget will always track the value of the
-variable, with no further intervention on your part.
-
-Keep a reference to the variable for as long as a widget uses it, for example
-by storing it as an attribute (see :class:`Variable`).
+Some widgets can tie their current value directly to a program variable,
+so that the two stay in sync.
+Options such as ``variable``, ``textvariable``, ``value``, ``onvalue`` and
+``offvalue`` set up this connection:
+when the user changes the widget the variable is updated,
+and when the variable is set the widget redraws to match.
+
+A widget can be linked only to a :class:`Variable` object,
+not to an ordinary Python variable.
+This is not a limitation of :mod:`!tkinter`
+but a consequence of how the two languages differ:
+the link relies on Tcl being notified every time the value changes,
+and Python offers no way to react when a plain variable is reassigned.
+A :class:`Variable` sidesteps this by keeping its value inside the Tcl interpreter
+and exposing it through explicit :meth:`~Variable.get` and :meth:`~Variable.set` methods.
+
+Ready-made subclasses cover the common types:
+:class:`StringVar`, :class:`IntVar`, :class:`DoubleVar` and :class:`BooleanVar`.
+Pass one as a widget's ``textvariable`` (or ``variable``) option,
+then read and update it with :meth:`~Variable.get` and :meth:`~Variable.set`;
+the widget tracks it with no further work on your part.
+
+Keep a reference to the variable for as long as the widget uses it
+-- for example by storing it as an attribute.
+A :class:`Variable` that is garbage collected removes its underlying Tcl variable,
+breaking the connection to the widget (see :class:`Variable`).
 
 For example::
 
    import tkinter as tk
+   from tkinter import ttk
 
-   class App(tk.Frame):
-       def __init__(self, master):
-           super().__init__(master)
-           self.pack()
+   root = tk.Tk()
 
-           self.entrythingy = tk.Entry()
-           self.entrythingy.pack()
+   # Create the application variable and give it an initial value.
+   contents = tk.StringVar(value="this is a variable")
 
-           # Create the application variable.
-           self.contents = tk.StringVar()
-           # Set it to some value.
-           self.contents.set("this is a variable")
-           # Tell the entry widget to watch this variable.
-           self.entrythingy["textvariable"] = self.contents
+   # Tell the entry widget to track the variable.
+   entry = ttk.Entry(root, textvariable=contents)
+   entry.pack()
 
-           # Define a callback for when the user hits return.
-           # It prints the current value of the variable.
-           self.entrythingy.bind('<Key-Return>',
-                                self.print_contents)
+   # Print the current value whenever the user presses Return.
+   def print_contents(event):
+       print("The current entry content is:", contents.get())
 
-       def print_contents(self, event):
-           print("Hi. The current entry content is:",
-                 self.contents.get())
+   entry.bind("<Return>", print_contents)
 
-   root = tk.Tk()
-   myapp = App(root)
-   myapp.mainloop()
+   # Setting the variable from the program updates the entry through the
+   # same link.
+   def clear():
+       contents.set("")
+
+   ttk.Button(root, text="Clear", command=clear).pack()
+
+   root.mainloop()
+
+.. _tkinter-window-manager:
 
 The window manager
 ^^^^^^^^^^^^^^^^^^
 
 .. index:: single: window manager (widgets)
 
-In Tk, there is a utility command, ``wm``, for interacting with the window
-manager.  Options to the ``wm`` command allow you to control things like titles,
-placement, icon bitmaps, and the like.  In :mod:`!tkinter`, these commands have
-been implemented as methods on the :class:`Wm` class.  Toplevel widgets are
-subclassed from the :class:`Wm` class, and so can call the :class:`Wm` methods
-directly.
-
-To get at the toplevel window that contains a given widget, you can often just
-refer to the widget's :attr:`~Tk.master`.
-Of course if the widget has been packed inside of a frame, the :attr:`!master`
-won't represent a toplevel window.
-To get at the toplevel window that contains an arbitrary widget, you can call
-the :meth:`~Misc.winfo_toplevel` method.
-There is also a :meth:`!_root` method; it begins with an underscore to denote
-the fact that this function is part of the implementation, and not an interface
-to Tk functionality.
-It returns the application's root window rather than the nearest enclosing
-toplevel.
-
-Here are some examples of typical usage::
+The *window manager* is the part of the desktop responsible for the title bar,
+border and controls drawn around each top-level window,
+and for such things as its title, position, size and icon.
+Tk gives access to these through the :class:`Wm` mixin,
+which is inherited by the :class:`Tk` root window and by every :class:`Toplevel`.
+You therefore call the window-manager methods directly on a top-level window.
+Each has a short name and an equivalent ``wm_``-prefixed name,
+for example :meth:`~Wm.title` and :meth:`~Wm.wm_title`.
+
+These methods act on the top-level window
+whether its content is built from the classic widgets or the themed :mod:`tkinter.ttk` widgets.
+To reach the top-level window containing an arbitrary widget,
+call its :meth:`~Misc.winfo_toplevel` method.
+
+For example::
 
    import tkinter as tk
+   from tkinter import ttk
 
-   class App(tk.Frame):
-       def __init__(self, master=None):
-           super().__init__(master)
-           self.pack()
+   root = tk.Tk()
+   root.title("My Application")
+   root.geometry("640x480")
+   root.minsize(320, 240)
 
-   # create the application
-   myapp = App()
+   ttk.Label(root, text="Hello").pack(padx=20, pady=20)
 
-   #
-   # here are method calls to the window manager class
-   #
-   myapp.master.title("My Do-Nothing Application")
-   myapp.master.maxsize(1000, 400)
+   root.mainloop()
 
-   # start the program
-   myapp.mainloop()
+See :class:`Wm` for the full set of window-manager methods.
 
 
 .. _Tk-option-data-types:
@@ -743,6 +767,9 @@ Tk option data types
 
 .. index:: single: Tk Option Data Types
 
+Many widget options documented in the reference
+accept values of a small number of common types, described here.
+
 anchor
    Legal values are points of the compass: ``"n"``, ``"ne"``, ``"e"``, ``"se"``,
    ``"s"``, ``"sw"``, ``"w"``, ``"nw"``, and also ``"center"``.
@@ -768,7 +795,8 @@ color
    Colors can be given as the names of X colors in the rgb.txt file, or as strings
    representing RGB values in 4 bit: ``"#RGB"``, 8 bit: ``"#RRGGBB"``, 12 bit:
    ``"#RRRGGGBBB"``, or 16 bit: ``"#RRRRGGGGBBBB"`` ranges, where R,G,B here
-   represent any legal hex digit.  See page 160 of Ousterhout's book for details.
+   represent any legal hex digit.  See the :manpage:`colors(3tk)` man page for
+   the list of named colors.
 
 cursor
    The name of the mouse cursor to display while the pointer is over the widget.
@@ -843,8 +871,7 @@ sequence
    ``<modifier-modifier-type-detail>`` form (for example ``"<Enter>"`` or
    ``"<Control-Button-1>"``); application-defined virtual events use double angle
    brackets, as in ``"<<Paste>>"``.  (See the
-   :manpage:`bind(3tk)` man page, and page 201 of John Ousterhout's book,
-   :title-reference:`Tcl and the Tk Toolkit (2nd edition)`, for details).
+   :manpage:`bind(3tk)` man page for details.)
 
 func
    is a Python function, taking one argument, to be invoked when the event occurs.
@@ -916,9 +943,32 @@ point at a specific place in a Text widget, or to particular characters in an
 Entry widget, or to particular menu items in a Menu widget.
 
 Entry widget indexes (index, view index, etc.)
-   Entry widgets have options that refer to character positions in the text being
-   displayed.  You can use these :mod:`!tkinter` functions to access these special
-   points in text widgets:
+   Entry widgets have methods and options that refer to character positions
+   in the text being displayed.
+   Anytime an index is needed, you may pass in:
+
+   * an integer which refers to the numeric position of a character,
+     counted from the beginning of the text, starting with 0;
+
+   * the string ``"anchor"``,
+     which refers to the anchor point of the selection,
+     set with the widget's selection methods;
+
+   * the string ``"end"``,
+     which refers to the position just after the last character;
+
+   * the string ``"insert"``,
+     which refers to the character just after the insertion cursor;
+
+   * the strings ``"sel.first"`` and ``"sel.last"``,
+     which refer to the first character in the selection
+     and the position just after the last
+     (it is an error to use these if there is no selection);
+
+   * a string consisting of ``@`` followed by an integer, as in ``"@6"``,
+     where the integer is interpreted as an x pixel coordinate
+     in the entry's coordinate system,
+     selecting the character spanning that point.
 
 Text widget indexes
    The index notation for Text widgets is very rich and is best described in the Tk
@@ -936,8 +986,9 @@ Menu indexes (menu.invoke(), menu.entryconfig(), etc.)
 
    * the string ``"last"`` which refers to the last menu item;
 
-   * An integer preceded by ``@``, as in ``@6``, where the integer is interpreted
-     as a y pixel coordinate in the menu's coordinate system;
+   * a string consisting of ``@`` followed by an integer, as in ``"@6"``, where
+     the integer is interpreted as a y pixel coordinate in the menu's coordinate
+     system;
 
    * the string ``"none"``, which indicates no menu entry at all, most often used
      with menu.activate() to deactivate all entries, and finally,
@@ -2241,6 +2292,7 @@ Base and mixin classes
    available on every top-level window.
    Each method has two equivalent spellings: a short name and a
    ``wm_``-prefixed name (for example, :meth:`title` and :meth:`wm_title`).
+   See also :ref:`tkinter-window-manager`.
 
    .. method:: wm_aspect(minNumer=None, minDenom=None, maxNumer=None, maxDenom=None)
       :no-typesetting:
@@ -2774,7 +2826,7 @@ Base and mixin classes
    The :class:`!Pack` mix-in is inherited by all widgets (through
    :class:`Widget`) and provides the methods for managing a widget with the
    *pack* geometry manager.
-   See also :ref:`pack-the-packer`.
+   See also :ref:`tkinter-geometry-management`.
 
    .. note::
 
@@ -2896,6 +2948,7 @@ Base and mixin classes
    their container.
    The :class:`!Place` mix-in is inherited by all widgets (through
    :class:`Widget`).
+   See also :ref:`tkinter-geometry-management`.
 
    .. method:: configure(cnf={}, **kw)
       :no-typesetting:
@@ -2979,6 +3032,7 @@ Base and mixin classes
    columns within their container.
    The :class:`!Grid` mix-in is inherited by all widgets (through
    :class:`Widget`).
+   See also :ref:`tkinter-geometry-management`.
 
    .. method:: configure(cnf={}, **kw)
       :no-typesetting:
index db5d547ddb66ae6b0ab99e6d749d09810f4a8df4..68ea834747ebfe7e36a47421d51179119bc2ba3b 100644 (file)
 --------------
 
 The :mod:`!tkinter.ttk` module provides access to the Tk themed widget set,
-introduced in Tk 8.5. It provides additional benefits including anti-aliased font
-rendering under X11 and window transparency (requiring a composition
-window manager on X11).
+introduced in Tk 8.5.
+Its widgets adapt their appearance to the platform's native theme,
+giving an application a better and more consistent look and feel
+than the classic :mod:`tkinter` widgets, whose appearance is fixed.
 
 The basic idea for :mod:`!tkinter.ttk` is to separate, to the extent possible,
 the code implementing a widget's behavior from the code implementing its
 appearance.
 
+Ttk widgets are used just like the classic :mod:`tkinter` widgets
+and share the same machinery:
+the widget hierarchy, the geometry managers, variable coupling and event binding.
+Those foundational concepts are covered in the :mod:`tkinter` documentation
+and are not repeated here.
+
 .. versionadded:: 3.1
 
 
 .. seealso::
 
-   `Tk Widget Styling Support <https://core.tcl.tk/tips/doc/trunk/tip/48.md>`_
-      A document introducing theming support for Tk
+   `Tk Widget Styling Support (TIP #48) <https://tip.tcl-lang.org/48.html>`_
+      The Tcl Improvement Proposal that introduced the themed widget styling engine.
 
 
 Using Ttk
@@ -49,21 +56,22 @@ That code causes several :mod:`!tkinter.ttk` widgets (:class:`Button`,
 :class:`Scrollbar` and :class:`Spinbox`) to
 automatically replace the Tk widgets.
 
+.. note::
+
+   Overriding the classic widgets with ``from tkinter.ttk import *``
+   is convenient for adapting existing code,
+   but new code is usually clearer if it imports the module
+   as ``from tkinter import ttk`` and refers to the themed widgets explicitly,
+   such as ``ttk.Button``.
+
 This has the direct benefit of using the new widgets which gives a better look
 and feel across platforms; however, the replacement widgets are not completely
 compatible.
-The main difference is that widget options such as "fg", "bg" and others
+The main difference is that widget options such as ``fg``, ``bg`` and others
 related to widget styling are no longer present in Ttk widgets.
 Instead, use the :class:`ttk.Style <Style>` class for improved styling effects.
 
 
-.. seealso::
-
-   `Converting existing applications to use Tile widgets <https://tktable.sourceforge.net/tile/doc/converting.txt>`_
-     A monograph (using Tcl terminology) about differences typically
-     encountered when moving applications to use the new widgets.
-
-
 Ttk widgets
 -----------
 
@@ -73,7 +81,7 @@ Ttk comes with 18 widgets, twelve of which already existed in tkinter:
 :class:`Radiobutton`, :class:`Scale`, :class:`Scrollbar`, and :class:`Spinbox`.
 The other six are new: :class:`Combobox`, :class:`Notebook`,
 :class:`Progressbar`, :class:`Separator`, :class:`Sizegrip` and
-:class:`Treeview`. And all them are subclasses of :class:`Widget`.
+:class:`Treeview`. All of them are subclasses of :class:`Widget`.
 
 Using the Ttk widgets gives the application an improved look and feel.
 As discussed above, there are differences in how the styling is coded.
@@ -1327,8 +1335,13 @@ If you don't know the class name of a widget, use the method
 
 .. seealso::
 
-   `Tcl'2004 conference presentation <https://tktable.sourceforge.net/tile/tile-tcl2004.pdf>`_
-      This document explains how the theme engine works
+   `Introduction to the Tk theme engine <https://www.tcl-lang.org/man/tcl9.0/TkCmd/ttk_intro.html>`_
+      The ``ttk::intro`` man page explains how the theme engine works.
+
+   `The Tile Widget Set <https://tktable.sourceforge.net/tile/tile-tcl2004.pdf>`_
+      Joe English's 2004 paper introducing the theme engine
+      (then the separate *Tile* extension),
+      with diagrams of how elements and layouts make up a widget's appearance.
 
 
 .. class:: Style