Skip to content

Textual 0.6.0 adds a treemendous new widget

A new release of Textual lands 3 weeks after the previous release -- and it's a big one.

Information

If you're new here, Textual is TUI framework for Python.

Tree Control

The headline feature of version 0.6.0 is a new tree control built from the ground-up. The previous Tree control suffered from an overly complex API and wasn't scalable (scrolling slowed down with 1000s of nodes).

This new version has a simpler API and is highly scalable (no slowdown with larger trees). There are also a number of visual enhancements in this version.

Here's a very simple example:

TreeApp ▼ Dune └── ▼ Characters ├── Paul ├── Jessica └── Chani

from textual.app import App, ComposeResult
from textual.widgets import Tree


class TreeApp(App):
    def compose(self) -> ComposeResult:
        tree: Tree[str] = Tree("Dune")
        tree.root.expand()
        characters = tree.root.add("Characters", expand=True)
        characters.add_leaf("Paul")
        characters.add_leaf("Jessica")
        characters.add_leaf("Chani")
        yield tree


if __name__ == "__main__":
    app = TreeApp()
    app.run()

Here's the tree control being used to navigate some JSON (json_tree.py in the examples directory).

I'm biased of course, but I think this terminal based tree control is more usable (and even prettier) than just about anything I've seen on the web or desktop. So much of computing tends to organize itself in to a tree that I think this widget will find a lot of uses.

The Tree control forms the foundation of the DirectoryTree widget, which has also been updated. Here it is used in the code_browser.py example:

List View

We have a new ListView control to navigate and select items in a list. Items can be widgets themselves, which makes this a great platform for building more sophisticated controls.

ListViewExample One Two Three ^p palette

from textual.app import App, ComposeResult
from textual.widgets import Footer, Label, ListItem, ListView


class ListViewExample(App):
    CSS_PATH = "list_view.tcss"

    def compose(self) -> ComposeResult:
        yield ListView(
            ListItem(Label("One")),
            ListItem(Label("Two")),
            ListItem(Label("Three")),
        )
        yield Footer()


if __name__ == "__main__":
    app = ListViewExample()
    app.run()

Placeholder

The Placeholder widget was broken since the big CSS update. We've brought it back and given it a bit of a polish.

Use this widget in place of custom widgets you have yet to build when designing your UI. The colors are automatically cycled to differentiate one placeholder from the next. You can click a placeholder to cycle between its ID, size, and lorem ipsum text.

PlaceholderApp Placeholder p2 here! This is a custom label for p1. #p4 #p3 #p5Placeholder Lorem ipsum dolor sit amet,  consectetur adipiscing elit.  Etiam feugiat ac elit sit amet  accumsan. Suspendisse bibendum  33 x 11nec libero quis gravida. 34 x 11 Phasellus id eleifend ligula.  Nullam imperdiet sem tellus,  sed vehicula nisl faucibus sit  amet. Praesent iaculis tempor  Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Etiam feugiat ac elit sit amet  accumsan. Suspendisse bibendum nec libero quis  gravida. Phasellus id eleifend ligula. Nullam  imperdiet sem tellus, sed vehicula nisl faucibus50 x 11 sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam  vel nisl. Curabitur vulputate erat pellentesque  mauris posuere, non dictum risus mattis. Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Etiam feugiat ac elit sit amet adipiscing elit. Etiam feugiat ac elit sit amet  accumsan. Suspendisse bibendum nec libero quis accumsan. Suspendisse bibendum nec libero quis  gravida. Phasellus id eleifend ligula. Nullam gravida. Phasellus id eleifend ligula. Nullam  imperdiet sem tellus, sed vehicula nisl faucibusimperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sedsit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapienlacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam congue mauris, sit amet pellentesque quam quam  vel nisl. Curabitur vulputate erat pellentesque vel nisl. Curabitur vulputate erat pellentesque  mauris posuere, non dictum risus mattis.mauris posuere, non dictum risus mattis.

from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, VerticalScroll
from textual.widgets import Placeholder


class PlaceholderApp(App):
    CSS_PATH = "placeholder.tcss"

    def compose(self) -> ComposeResult:
        yield VerticalScroll(
            Container(
                Placeholder("This is a custom label for p1.", id="p1"),
                Placeholder("Placeholder p2 here!", id="p2"),
                Placeholder(id="p3"),
                Placeholder(id="p4"),
                Placeholder(id="p5"),
                Placeholder(),
                Horizontal(
                    Placeholder(variant="size", id="col1"),
                    Placeholder(variant="text", id="col2"),
                    Placeholder(variant="size", id="col3"),
                    id="c1",
                ),
                id="bot",
            ),
            Container(
                Placeholder(variant="text", id="left"),
                Placeholder(variant="size", id="topright"),
                Placeholder(variant="text", id="botright"),
                id="top",
            ),
            id="content",
        )


if __name__ == "__main__":
    app = PlaceholderApp()
    app.run()

Fixes

As always, there are a number of fixes in this release. Mostly related to layout. See CHANGELOG.md for the details.

What's next?

The next release will focus on pain points we discovered while in a dog-fooding phase (see the DevLog for details on what Textual devs have been building).