Tkhtml 3.0 is a new html rendering widget for Tk. It is a rewrite of the existing Tkhtml widget 2.0 that is included with ActiveTcl and other batteries-included distributions. This page is a tutorial that showcases the capabilities and design of the new widget without getting bogged down in details. Instead of explaining interfaces there are just code fragments. Please leap in to ask questions, make suggestions or heap ridicule.

The development of Tkhtml 3.0 was sponsored by Eolas.

APN License is, curiously enough, LGPL. Strange, coming from drh.


The sources are available on this fossil repository: sources . You need to login as anonymous on that repository in order to get the links for downloading ZIP or tarball archives. Being logged in, you can go to the 'Timeline', click on the linked hexadecimal check-in number and then just choose the download you want to have. The lastest check-in is from 2011 (9 Jan): [L1 ]

Note that the 'Sources' link given at is out of date.


official reference


By itself, Tkhtml 3.0 doesn't provide much of the functionality that embedding a web browser would. Tkhtml has no idea that there is anything special about an <a> tag, let alone the "href" attribute, so clicking on a hyper-link does nothing by default. Without extra configuration, Tkhtml doesn't understand the significance of a <style>, <title> or <img> tag.

Instead, the widget concentrates on providing interfaces that allow the majority of policy decisions to be made by the user. For example, the widget can be queried for the document node that generated the content at any point in the viewport. When an end-user makes a mouse-click, the script can query the html widget for the relevant document node and decide for itself if a hyper-link should be followed, some javascript executed, the selection cleared, or some other application specific action.

If an application designer deems that the contents of any <style> tags in the document should be parsed as CSS style sheets, they configure Tkhtml to pass the contents of each parsed <style> tag to a handler script that in turn passes it back to the Tkhtml style command.

Tkhtml doesn't really have too much of a dependancy on HTML either. Most HTML behaviour, i.e. the fact that a <b> tag means use bold font, is configured using a built-in default stylesheet document. Execute the following script if you want to see this document:

package require Tkhtml 3.0
html .h
.h cget -defaultstyle

Right now the parser is a holdover from Tkhtml 2.0 and only recognizes valid html tags. But in the long run it will be possible to display any old XML document formatted using CSS stylesheets.

Tkhtml is designed with the expectation that most users will use a megawidget that provides many of these features. A megawidget to support common HTML constructs, Hv3, is being developed and distributed as part of Tkhtml, But Tkhtml is useful by itself as well.

Running the Examples

hv3 is a demo application of Tkhtml 3. starkits are available that wrap Tkhtml into a minimal web browser so you can see how it goes on complicated web pages available via HTTP. There are screenshots there too.

A Fancy Label Widget

The simplest use of the html widget is as a fancy label widget. For example:

# Load the Tkhtml package
package require Tkhtml 3.0

# Create and populate an html widget.
html .label -shrink 1
.label parse -final {
    <b>Hello <i>world</i></b> example

# Pack the new html widget
pack .label
bind .label <KeyPress-q> exit
focus .label

This code creates and packs a somewhat unremarkable label containing the HTML-formatted text. There are no default bindings, those all have to be supplied by the user.

The most visible difference between Tkhtml 3.0 and it's predecessor is that Tkhtml supports stylesheets and the "style" attribute. To illustrate:

package require Tkhtml 3.0

# Create and populate an html widget
html .button -shrink 1
.button parse -final {<html><div>Exit!</div> Click to Exit :)}
.button style {
    /* This is a CSS style sheet */
    div {
        border:solid 2px;
        border-color: white grey30 grey30 white;
        background: grey85;
        padding: 5px 0px;
        margin: 0px auto;
        text-align: center;
        width: 10ex;
    html {
        background: grey85;
        font-family: Helvetica;
        font-weight: bold;
        padding: 5px;

# Pack the new html widget
pack .button
bind .button <1> exit
focus .button

Tkhtml 3.0 supports most of the CSS 1.0 properties and selectors, giving the programmer good control over the document layout. A precise description of what is supported and what is not may be found at .

A Geometry Manager

Tkhtml is good for more than just static text and borders, it can also manage other Tk widgets. Here's a simple example - this time featuring a real live button, not a fake like before.

package require Tkhtml 3.0

# Create and populate an html widget
html .manager -shrink 1
.manager parse -final {
        widgetcmd="button .manager.button -text Exit! -command exit"
        style="margin: auto"
    Click the above button to exit.

# The tricky bit!
foreach nodeHandle [.manager search {[widgetcmd]}] {
    $nodeHandle replace [eval [$nodeHandle attribute widgetcmd]]

# Pack the new html widget
pack .manager

Once Tkhtml has parsed a document, it exposes the tree structure to the application via "node-handles". Each node-handle is itself a Tcl command, with a set of sub-commands that can be used to query and manipulate the document node. For example the command:

$nodeHandle attribute widgetcmd

returns the value of the "widgetcmd" attribute of node $nodeHandle. Assuming the variable $nodeHandle contains the node-handle for the <div> in the example above, this command would return the string "button .manager.button -text Exit! -command exit".

The command:

$nodeHandle replace $widget

tells Tkhtml that instead of drawing content for the node $nodeHandle, map the Tk window $widget into the html display. The other new command introduced here is:

$html search $selector

This command searches the document tree for nodes that match the criteria specified by $selector. The format for the search criteria is a CSS 1.0 selector. The selector used in the above example, "widgetcmd" matches all nodes that have an attribute named "widgetcmd".

Here's a more elaborate example of the same idea:

package require Tkhtml 3.0

# Create and populate an html widget
html .manager -shrink 1
.manager parse -final {
        <h1 style="text-align:center">XYZ Company Complaints Interface</h1>
        <table border=0 align=center style="border:1px solid">
                <td>First Name<span style="color:red">*</span>:
                <td><div widgetcmd="entry .manager.entry1">
                <td>Last Name<span style="color:red">*</span>:
                <td><div widgetcmd="entry .manager.entry2">
                <td><div widgetcmd="entry .manager.entry3">
        <div widgetcmd="text .manager.text" />
        <table border=0 align=center cellpadding=10 style="border:1px solid"><tr>
        <td><div widgetcmd="button .manager.send -text Send -width 20" />
        <td><div widgetcmd="button .manager.cancel -text Cancel -width 20" />

# Trickery!
foreach nodeHandle [.manager search {[widgetcmd]}] {
  $nodeHandle replace [eval [$nodeHandle attribute widgetcmd]]

.manager.text insert 0.0 "Enter message here."

# Pack the new html widget
pack .manager -fill both -expand true

Please Note: The code in the above examples executes Tcl scripts embedded in the document passed to the html widget. This is Ok, because in this case the document is embedded in the application itself. There is no way for the html widget to get hold of an external document that may contain malicious scripts. It would be very foolhardy indeed to use the same techniques in an application that might obtain documents from external sources. Cough... internet explorer cough...

Tkhtml provides no special support for html forms other than the ability to replace document nodes with Tkhtml windows as demonstrated above. Implementing that sort of thing is up to applications or megawidget frameworks. This allows the same "replace-node" interface to be used for web plugin implementations, or to embed a canvas widget configured to render an embedded SVG image.

Tkhtml does provide ways to register scripts for execution:

  • When a replacement window is no longer required (e.g. because a newdocument is loaded),
  • To configure the colors, fonts etc. of replaced windows according to the document and stylesheets, and
  • As soon a document node that matches a specified selector has been parsed (allows for incremental parsing of documents with embedded forms).

See the full widget man page at for details.

Displaying Images

Html documents aren't much fun without images. With a little help, Tkhtml can support background-images, list-marker images and images used to replace entire nodes. Here's an example:

image create photo idir -data {
image create photo ibg -data {

package require Tkhtml 3.0

# Create and populate an html widget
html .images -shrink 1 -imagecmd get_image -width 200
.images parse -final {
    <body style="background-image: url(ibg); color: white ; font-weight:bold">
    <img src="idir" width=100 height=50 align=left>
    Html documents aren't much fun without images. With a little help, Tkhtml
    can support background-images, list-marker images and images used to
    replace entire nodes. The example only shows backgrounds and replaced
proc get_image {uri} {return $uri}

pack .images
bind .images <KeyPress-q> exit
focus .images

The -imagecmd option of the html widget takes a script. When the widget finds an image URI, it appends the URI to the script and executes it. The script should return the name of a Tk image to be used by the html widget. The image may be populated or overwritten by the script at a later time and the widget display is automatically updated.

By default, Tkhtml deletes images when it has finished using them but it can be configured to invoke a user-provided script instead. There are also interfaces to ensure that relative URIs can be interpreted unambiguously (for example a relative URI from an external stylesheet may be interpreted differently to a relative URI embedded in the html document itself).

Here's a slightly fancier, more complete get_image procedure that will let you specify a Tk image, a file, or a http url. it needs some error checking incase the file or url specified is not actually an image. It has the added bonus of not creating a new Tk image for repeat use of the same file or url.

package require Img
package require http

proc get_url url {
       set token [::http::geturl $url]
       set data [::http::data $token]
       ::http::cleanup $token
       return $data

proc get_image uri {
     #if the 'url' passed is an image name
     if { [lsearch [image names]  $uri] > -1 } {
          return $uri

     # if the 'url' passed is a file on disk
     if { [file exists $uri] } {
          #create image using file
          image create photo  $uri -file $uri
          return $uri

     #if the 'url' is an http url. 
     if { [string equal -length 7 $uri http://] } {
          image create photo $uri -data [get_url $uri]
          return $uri

Please post any bugs you find with Tkhtml at this website:


rdt I built this on a Mandrake 2006.0 distro and checked out all these snippets. It's nice. Thanks.

See also

The main page for Tkhtml

JOB - 2017-01-23

A TclOO Tkhtml 3.0 megawidget - example of how to render html+css

fpigorsch - 2017-08-16 13:35:00 seems to be down. Anyone knows what's going on there? has a relatively recent snapshot of the original repo.

ak - 2017-08-22 19:56:12

Machines moved, updating the DNS records lagged. Everything should be ok again.

"stlgy - 2022-02-15"

Some websites are now increasingly using images in svg format instead of gif/png/jpg formats. The thing with the svg is that the image data tends to be inline and the url part for these seem to be slightly different from what is expected. An example is below.

Does Tk support svg format so that one could convert the svg data into an image so Tkhtml can display it?

Example svg data as encountered by tkhtml:

Unsupported URL: data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMzQ4LjMzM3B4IiBoZWlnaHQ9IjM0OC4zMzNweCIgdmlld0JveD0iMCAwIDM0OC4zMzMgMzQ4LjMzNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzQ4LjMzMyAzNDguMzM0OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PHBhdGggZmlsbD0iIzU2NTY1NiIgZD0iTTMzNi41NTksNjguNjExTDIzMS4wMTYsMTc0LjE2NWwxMDUuNTQzLDEwNS41NDljMTUuNjk5LDE1LjcwNSwxNS42OTksNDEuMTQ1LDAsNTYuODVjLTcuODQ0LDcuODQ0LTE4LjEyOCwxMS43NjktMjguNDA3LDExLjc2OWMtMTAuMjk2LDAtMjAuNTgxLTMuOTE5LTI4LjQxOS0xMS43NjlMMTc0LjE2NywyMzEuMDAzTDY4LjYwOSwzMzYuNTYzYy03Ljg0Myw3Ljg0NC0xOC4xMjgsMTEuNzY5LTI4LjQxNiwxMS43NjljLTEwLjI4NSwwLTIwLjU2My0zLjkxOS0yOC40MTMtMTEuNzY5Yy0xNS42OTktMTUuNjk4LTE1LjY5OS00MS4xMzksMC01Ni44NWwxMDUuNTQtMTA1LjU0OUwxMS43NzQsNjguNjExYy0xNS42OTktMTUuNjk5LTE1LjY5OS00MS4xNDUsMC01Ni44NDRjMTUuNjk2LTE1LjY4Nyw0MS4xMjctMTUuNjg3LDU2LjgyOSwwbDEwNS41NjMsMTA1LjU1NEwyNzkuNzIxLDExLjc2N2MxNS43MDUtMTUuNjg3LDQxLjEzOS0xNS42ODcsNTYuODMyLDBDMzUyLjI1OCwyNy40NjYsMzUyLjI1OCw1Mi45MTIsMzM2LjU1OSw2OC42MTF6Ii8+PC9nPjwvc3ZnPg==
    while executing
"::http::geturl $uri        ..."

Source URL: