Textualize/textual · error · ValueError

No Tab with id {active!r}

Error message

No Tab with id {active!r}

What it means

Raised by Tabs.validate_active when the active reactive is set to a non-empty id that does not match any Tab in the tabs-list. The validator queries `#tabs-list > #{active}` and rejects unknown ids immediately.

Source

Thrown at src/textual/widgets/_tabs.py:580

        else:
            next_tab = None

        async def do_remove() -> None:
            """Perform the remove after refresh so the underline bar gets new positions."""
            await remove_tab.remove()
            if not self.query("#tabs-list > Tab"):
                self.active = ""
            elif next_tab is not None:
                self.active = next_tab.id or ""
            else:
                self._highlight_active(animate=False)

        return AwaitComplete(do_remove())

    def validate_active(self, active: str) -> str:
        """Check id assigned to active attribute is a valid tab."""
        if active and not self.query(f"#tabs-list > #{active}"):
            raise ValueError(f"No Tab with id {active!r}")
        return active

    @property
    def active_tab(self) -> Tab | None:
        """The currently active tab, or None if there are no active tabs."""
        try:
            return self.query_one("#tabs-list Tab.-active", Tab)
        except NoMatches:
            return None

    def _on_mount(self, _: Mount) -> None:
        """Make the first tab active."""
        if self._first_active is not None:
            self.active = self._first_active
        if not self.active:
            try:
                tab = self.query("#tabs-list > Tab").first(Tab)
            except NoMatches:

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Ensure the Tab with that id exists (added as a child) before setting active.
  2. Use the tab's id exactly (ids are case-sensitive and become CSS ids).
  3. When restoring state, verify the id still exists or fall back to the first tab.

Example fix

# before
tabs = Tabs(active='settings')
tabs.add_tab(Tab('Settings', id='settings'))  # too late? ensure order/await
# after
tabs = Tabs(Tab('Settings', id='settings'), active='settings')
Defensive patterns

Strategy: try-catch

Validate before calling

if not tabs.query(f'#tabs-list > #{tab_id}'):
    tab_id = ''  # or first tab id

Try / catch

try:
    tabs.active = saved_id
except ValueError:
    tabs.active = ''

Prevention

When it happens

Trigger: Tabs(active='missing-id') when no Tab with that id was added; tabs.active = 'foo' before the Tab('...', id='foo') is added; or after removing the active tab and setting a stale id.

Common situations: Restoring a persisted active tab id after the tab set changed, ordering issues where active is set before children are added, typos in ids.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/b1dfc269e0c47c43. Report an issue: GitHub.