{"record":{"id":"370724d88a72f608","repo":"Textualize/textual","slug":"node-has-no-screen","errorCode":null,"errorMessage":"node has no screen","messagePattern":"node has no screen","errorType":"exception","errorClass":"NoScreen","httpStatus":null,"severity":"error","filePath":"src/textual/dom.py","lineNumber":806,"sourceCode":"            A screen object.\n\n        Raises:\n            NoScreen: If this node isn't mounted (and has no screen).\n        \"\"\"\n        # Get the node by looking up a chain of parents\n        # Note that self.screen may not be the same as self.app.screen\n        from textual.screen import Screen\n\n        node: MessagePump | None = self\n        try:\n            while node is not None and not isinstance(node, Screen):\n                node = node._parent\n        except AttributeError:\n            raise RuntimeError(\n                \"Widget is missing attributes; have you called the constructor in your widget class?\"\n            ) from None\n        if not isinstance(node, Screen):\n            raise NoScreen(\"node has no screen\")\n        return node\n\n    @property\n    def id(self) -> str | None:\n        \"\"\"The ID of this node, or None if the node has no ID.\"\"\"\n        return self._id\n\n    @id.setter\n    def id(self, new_id: str) -> str:\n        \"\"\"Sets the ID (may only be done once).\n\n        Args:\n            new_id: ID for this node.\n\n        Raises:\n            ValueError: If the ID has already been set.\n        \"\"\"\n        check_identifiers(\"id\", new_id)","sourceCodeStart":788,"sourceCodeEnd":824,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/dom.py#L788-L824","documentation":"NoScreen is raised by the screen property when the node has no Screen ancestor: the widget is not mounted on a running app/screen, so no screen can be resolved by walking _parent links.","triggerScenarios":"Accessing widget.screen (or APIs that use it: screen.refresh, posting to screen, styles resolution) before the widget is mounted, e.g. inside __init__ or compose() before the widget is attached to a Screen.","commonSituations":"Calling self.screen in a widget constructor or in compose(); using widgets standalone in unit tests without an app runner; accessing a removed widget after remove() breaks the parent chain.","solutions":["Move code that needs self.screen out of __init__ into on_mount or a lifecycle hook that runs after mounting","In tests, use textual's run_test() async context / Pilot so widgets are mounted on a screen","If the widget may be detached, check for a screen defensively before use"],"exampleFix":"# before\nclass MyWidget(Widget):\n    def __init__(self):\n        super().__init__()\n        self.bg = self.screen.background  # NoScreen\n\n# after\nclass MyWidget(Widget):\n    def on_mount(self) -> None:\n        self.bg = self.screen.background","handlingStrategy":"validation","validationCode":"def safe_screen(widget):\n    node = widget\n    while node is not None and not isinstance(node, Screen):\n        node = node._parent\n    return node  # None if not mounted\n\nscr = safe_screen(widget)\nif scr is None:\n    defer_work_until_mount()","typeGuard":"from textual.screen import Screen\ndef is_mounted(widget) -> bool:\n    return widget.screen is not None if widget.is_mounted else False","tryCatchPattern":"from textual.screen import NoScreen\ntry:\n    scr = widget.screen\nexcept NoScreen:\n    schedule_after_mount(widget)  # retry later","preventionTips":["Never access self.screen in __init__ or compose","Use on_mount for screen-dependent logic","Use run_test()/Pilot in tests so screens exist"],"tags":["textual","mount","screen","lifecycle"],"backgroundTag":"widget-not-mounted","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}