Skip to content

TabbedContent

Added in version 0.16.0

Switch between mutually exclusive content panes via a row of tabs.

  • Focusable
  • Container

This widget combines the Tabs and ContentSwitcher widgets to create a convenient way of navigating content.

Only a single child of TabbedContent is visible at once. Each child has an associated tab which will make it visible and hide the others.

Composing

There are two ways to provide the titles for the tab. You can pass them as positional arguments to the TabbedContent constructor:

def compose(self) -> ComposeResult:
    with TabbedContent("Leto", "Jessica", "Paul"):
        yield Markdown(LETO)
        yield Markdown(JESSICA)
        yield Markdown(PAUL)

Alternatively you can wrap the content in a TabPane widget, which takes the tab title as the first parameter:

def compose(self) -> ComposeResult:
    with TabbedContent():
        with TabPane("Leto"):
            yield Markdown(LETO)
        with TabPane("Jessica"):
            yield Markdown(JESSICA)
        with TabPane("Paul"):
            yield Markdown(PAUL)

Switching tabs

If you need to programmatically switch tabs, you should provide an id attribute to the TabPanes.

def compose(self) -> ComposeResult:
    with TabbedContent():
        with TabPane("Leto", id="leto"):
            yield Markdown(LETO)
        with TabPane("Jessica", id="jessica"):
            yield Markdown(JESSICA)
        with TabPane("Paul", id="paul"):
            yield Markdown(PAUL)

You can then switch tabs by setting the active reactive attribute:

# Switch to Jessica tab
self.query_one(TabbedContent).active = "jessica"

Note

If you don't provide id attributes to the tab panes, they will be assigned sequentially starting at tab-1 (then tab-2 etc).

Initial tab

The first child of TabbedContent will be the initial active tab by default. You can pick a different initial tab by setting the initial argument to the id of the tab:

def compose(self) -> ComposeResult:
    with TabbedContent(initial="jessica"):
        with TabPane("Leto", id="leto"):
            yield Markdown(LETO)
        with TabPane("Jessica", id="jessica"):
            yield Markdown(JESSICA)
        with TabPane("Paul", id="paul"):
            yield Markdown(PAUL)

Example

The following example contains a TabbedContent with three tabs.

TabbedApp LetoJessicaPaul ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Lady Jessica ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Bene Gesserit and concubine of Leto, and mother of Paul and Alia. PaulAlia ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ First child  L  Leto  J  Jessica  P  Paul 

from textual.app import App, ComposeResult
from textual.widgets import Footer, Label, Markdown, TabbedContent, TabPane

LETO = """
# Duke Leto I Atreides

Head of House Atreides.
"""

JESSICA = """
# Lady Jessica

Bene Gesserit and concubine of Leto, and mother of Paul and Alia.
"""

PAUL = """
# Paul Atreides

Son of Leto and Jessica.
"""


class TabbedApp(App):
    """An example of tabbed content."""

    BINDINGS = [
        ("l", "show_tab('leto')", "Leto"),
        ("j", "show_tab('jessica')", "Jessica"),
        ("p", "show_tab('paul')", "Paul"),
    ]

    def compose(self) -> ComposeResult:
        """Compose app with tabbed content."""
        # Footer to show keys
        yield Footer()

        # Add the TabbedContent widget
        with TabbedContent(initial="jessica"):
            with TabPane("Leto", id="leto"):  # First tab
                yield Markdown(LETO)  # Tab content
            with TabPane("Jessica", id="jessica"):
                yield Markdown(JESSICA)
                with TabbedContent("Paul", "Alia"):
                    yield TabPane("Paul", Label("First child"))
                    yield TabPane("Alia", Label("Second child"))

            with TabPane("Paul", id="paul"):
                yield Markdown(PAUL)

    def action_show_tab(self, tab: str) -> None:
        """Switch to a new tab."""
        self.get_child_by_type(TabbedContent).active = tab


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

Reactive attributes

Name Type Default Description
active str "" The id attribute of the active tab. Set this to switch tabs.

Messages

See also


textual.widgets.TabbedContent class

def __init__(
    self,
    *titles,
    initial="",
    name=None,
    id=None,
    classes=None,
    disabled=False
):

Bases: Widget

A container with associated tabs to toggle content visibility.

Parameters
Name Type Description Default
*titles TextType

Positional argument will be used as title.

()
initial str

The id of the initial tab, or empty string to select the first tab.

''
name str | None

The name of the button.

None
id str | None

The ID of the button in the DOM.

None
classes str | None

The CSS classes of the button.

None
disabled bool

Whether the button is disabled or not.

False

active class-attribute instance-attribute

active: reactive[str] = reactive('', init=False)

The ID of the active tab, or empty string if none are active.

TabActivated class

def __init__(self, tabbed_content, tab):

Bases: Message

Posted when the active tab changes.

Parameters
Name Type Description Default
tabbed_content TabbedContent

The TabbedContent widget.

required
tab Tab

The Tab widget that was selected (contains the tab label).

required

ALLOW_SELECTOR_MATCH class-attribute instance-attribute

ALLOW_SELECTOR_MATCH = {'tab'}

Additional message attributes that can be used with the on decorator.

control property

control: TabbedContent

The TabbedContent widget that contains the tab activated.

This is an alias for TabActivated.tabbed_content and is used by the on decorator.

tab instance-attribute

tab = tab

The Tab widget that was selected (contains the tab label).

tabbed_content instance-attribute

tabbed_content = tabbed_content

The TabbedContent widget that contains the tab activated.

compose method

def compose(self):

Compose the tabbed content.

compose_add_child method

def compose_add_child(self, widget):

When using the context manager compose syntax, we want to attach nodes to the switcher.

Parameters
Name Type Description Default
widget Widget

A Widget to add.

required

validate_active method

def validate_active(self, active):

It doesn't make sense for active to be an empty string.

Parameters
Name Type Description Default
active str

Attribute to be validated.

required
Returns
Type Description
str

Value of active.

Raises
Type Description
ValueError

If the active attribute is set to empty string.

watch_active method

def watch_active(self, active):

Switch tabs when the active attributes changes.


textual.widgets.TabPane class

def __init__(
    self,
    title,
    *children,
    name=None,
    id=None,
    classes=None,
    disabled=False
):

Bases: Widget

A container for switchable content, with additional title.

This widget is intended to be used with TabbedContent.

Parameters
Name Type Description Default
title TextType

Title of the TabPane (will be displayed in a tab label).

required
*children Widget

Widget to go inside the TabPane.

()
name str | None

Optional name for the TabPane.

None
id str | None

Optional ID for the TabPane.

None
classes str | None

Optional initial classes for the widget.

None
disabled bool

Whether the TabPane is disabled or not.

False