Textualize/textual · error · ValueError

{variable_name} should contain a Screen type or callable, no

Error message

{variable_name} should contain a Screen type or callable, not an instance (got instance of {type(screen_object).__name__} for {screen_name!r})

What it means

Raised in App.__init_subclass__ when a SCREENS or MODES dict entry is an instantiated Screen object. Textual requires the class/callable (factory) so it can construct fresh screens, not a shared instance.

Source

Thrown at src/textual/app.py:920

    @property
    def _is_devtools_connected(self) -> bool:
        """Is the app connected to the devtools?"""
        return self.devtools is not None and self.devtools.is_connected

    @cached_property
    def _exception_event(self) -> asyncio.Event:
        """An event that will be set when the first exception is encountered."""
        return asyncio.Event()

    def __init_subclass__(cls, *args, **kwargs) -> None:
        for variable_name, screen_collection in (
            ("SCREENS", cls.SCREENS),
            ("MODES", cls.MODES),
        ):
            for screen_name, screen_object in screen_collection.items():
                if not (isinstance(screen_object, str) or callable(screen_object)):
                    if isinstance(screen_object, Screen):
                        raise ValueError(
                            f"{variable_name} should contain a Screen type or callable, not an instance"
                            f" (got instance of {type(screen_object).__name__} for {screen_name!r})"
                        )
                    raise TypeError(
                        f"expected a callable or string, got {screen_object!r}"
                    )

        return super().__init_subclass__(*args, **kwargs)

    def _thread_init(self):
        """Initialize threading primitives for the current thread.

        https://github.com/Textualize/textual/issues/5845

        """
        self._message_queue
        self._mounted_event
        self._exception_event

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Store the Screen subclass itself: SCREENS = {"main": MainScreen}
  2. Or store a callable/string: SCREENS = {"main": "other_screen_name"} or a lambda returning a Screen

Example fix

# before
class MyApp(App):
    SCREENS = {"main": MainScreen()}

# after
class MyApp(App):
    SCREENS = {"main": MainScreen}
Defensive patterns

Strategy: validation

Validate before calling

for name, s in SCREENS.items():
    assert isinstance(s, str) or (callable(s) and not isinstance(s, Screen)), f'{name}: pass the class, not an instance'

Type guard

def is_valid_screen_entry(v) -> bool:
    return isinstance(v, str) or (callable(v) and not isinstance(getattr(v, '__self__', None), type) and not isinstance(v, Screen))

Prevention

When it happens

Trigger: Writing SCREENS = {"main": MainScreen()} on an App subclass — the class-level validation on subclass creation detects the instance.

Common situations: Migrating code that stored pre-built screen instances; misunderstanding SCREENS as a registry of live objects rather than factories.

Related errors


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