Skip to content


Displays a number of tab headers which may be activated with a click or navigated with cursor keys.

  • Focusable
  • Container

Construct a Tabs widget with strings or Text objects as positional arguments, which will set the labels in the tabs. Here's an example with three tabs:

def compose(self) -> ComposeResult:
    yield Tabs("First tab", "Second tab", Text.from_markup("[u]Third[/u] tab"))

This will create Tab widgets internally, with auto-incrementing id attributes ("tab-1", "tab-2" etc). You can also supply Tab objects directly in the constructor, which will allow you to explicitly set an id. Here's an example:

def compose(self) -> ComposeResult:
    yield Tabs(
        Tab("First tab", id="one"),
        Tab("Second tab", id="two"),

When the user switches to a tab by clicking or pressing keys, then Tabs will send a Tabs.TabActivated message which contains the tab that was activated. You can then use attribute to perform any related actions.

Clearing tabs

Clear tabs by calling the clear method. Clearing the tabs will send a Tabs.TabActivated message with the tab attribute set to None.

Adding tabs

Tabs may be added dynamically with the add_tab method, which accepts strings, Text, or Tab objects.


The following example adds a Tabs widget above a text label. Press A to add a tab, C to clear the tabs.

TabsApp Paul AtreidiesDuke Leto AtreidesLady JessicaGurney HalleckBaron Vl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Lady Jessica ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁  A  Add tab  R  Remove active tab  C  Clear tabs 

from import App, ComposeResult
from textual.widgets import Footer, Label, Tabs

    "Paul Atreidies",
    "Duke Leto Atreides",
    "Lady Jessica",
    "Gurney Halleck",
    "Baron Vladimir Harkonnen",
    "Glossu Rabban",

class TabsApp(App):
    """Demonstrates the Tabs widget."""

    CSS = """
    Tabs {
        dock: top;
    Screen {
        align: center middle;
    Label {
        margin:1 1;
        width: 100%;
        height: 100%;
        background: $panel;
        border: tall $primary;
        content-align: center middle;

    BINDINGS = [
        ("a", "add", "Add tab"),
        ("r", "remove", "Remove active tab"),
        ("c", "clear", "Clear tabs"),

    def compose(self) -> ComposeResult:
        yield Tabs(NAMES[0])
        yield Label()
        yield Footer()

    def on_mount(self) -> None:
        """Focus the tabs when the app starts."""

    def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
        """Handle TabActivated message sent by Tabs."""
        label = self.query_one(Label)
        if is None:
            # When the tabs are cleared, will be None
            label.visible = False
            label.visible = True

    def action_add(self) -> None:
        """Add a new tab."""
        tabs = self.query_one(Tabs)
        # Cycle the names
        NAMES[:] = [*NAMES[1:], NAMES[0]]

    def action_remove(self) -> None:
        """Remove active tab."""
        tabs = self.query_one(Tabs)
        active_tab = tabs.active_tab
        if active_tab is not None:

    def action_clear(self) -> None:
        """Clear the tabs."""

if __name__ == "__main__":
    app = TabsApp()

Reactive Attributes

Name Type Default Description
active str "" The ID of the active tab. Set this attribute to a tab ID to change the active tab.



Bases: Message

Sent when a new tab is activated.

__init__(tabs, tab)

Initialize event.


Name Type Description Default
tabs Tabs

The Tabs widget.

tab Tab

The tab that was activated.



Bases: Message

Sent when there are no active tabs.


Initialize the event.


Name Type Description Default
tabs Tabs

The tabs widget.



The Tabs widget defines the following bindings:

Key(s) Description
left Move to the previous tab.
right Move to the next tab.

See Also

  • Tabs code reference