{"record":{"id":"d9c6e5e335bb5915","repo":"Textualize/textual","slug":"tried-to-insert-a-widget-with-id-widget-id-r-bu","errorCode":null,"errorMessage":"Tried to insert a widget with ID {widget_id!r}, but a widget already exists with that ID ({self._nodes_by_id[widget_id]!r}); ensure all child widgets have a unique ID.","messagePattern":"Tried to insert a widget with ID (.+?), but a widget already exists with that ID \\((.+?)\\); ensure all child widgets have a unique ID\\.","errorType":"exception","errorClass":"DuplicateIds","httpStatus":null,"severity":"error","filePath":"src/textual/_node_list.py","lineNumber":158,"sourceCode":"            self._nodes.insert(index, widget)\n            self._nodes_set.add(widget)\n            widget_id = widget.id\n            if widget_id is not None:\n                self._ensure_unique_id(widget_id)\n                self._nodes_by_id[widget_id] = widget\n            self.updated()\n\n    def _ensure_unique_id(self, widget_id: str) -> None:\n        \"\"\"Ensure a new widget ID would be unique.\n\n        Args:\n            widget_id: New widget ID.\n\n        Raises:\n            DuplicateIds: If the given ID is not unique.\n        \"\"\"\n        if widget_id in self._nodes_by_id:\n            raise DuplicateIds(\n                f\"Tried to insert a widget with ID {widget_id!r}, but a widget already exists with that ID ({self._nodes_by_id[widget_id]!r}); \"\n                \"ensure all child widgets have a unique ID.\"\n            )\n\n    def _remove(self, widget: Widget) -> None:\n        \"\"\"Remove a widget from the list.\n\n        Removing a widget not in the list is a null-op.\n\n        Args:\n            widget: A Widget in the list.\n        \"\"\"\n        if widget in self._nodes_set:\n            del self._nodes[self._nodes.index(widget)]\n            self._nodes_set.remove(widget)\n            widget_id = widget.id\n            if widget_id in self._nodes_by_id:\n                del self._nodes_by_id[widget_id]","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/_node_list.py#L140-L176","documentation":"Textual's NodeList._ensure_unique_id runs every time a widget is appended or inserted into a container, and raises DuplicateIds if the incoming widget's id already exists among that container's direct children. IDs in Textual are only required to be unique among siblings of the same parent widget, not globally. The message includes both the requested id and a repr of the existing widget that already claims it, which makes locating the collision straightforward.","triggerScenarios":"Mounting two widgets with the same id into the same parent: `mount(Static(\"a\", id=\"x\"), Static(\"b\", id=\"x\"))`; mounting a widget that was constructed with an id equal to one used by a sibling; loops that generate ids like f\"row{i}\" with colliding counters; compositing the same widget instance twice without removing it first.","commonSituations":"Data-driven UIs that build rows keyed by a non-unique field (e.g. two rows with the same category id); copy-pasted widget definitions retaining the same id; widgets re-mounted after remove() in refresh loops where removal hasn't completed; two different components each defaulting to id=\"root\" or id=\"container\" mounted into the same screen.","solutions":["Give each sibling a unique id, e.g. derive it from a unique key: f\"row-{item['pk']}\"","If the same widget is re-mounted, call await widget.remove() first, or use replace_children/mount with `before`/`after` on a fresh instance","Use query_one('#id') on the right parent scope to confirm which container already holds the id shown in the error","Omit ids entirely and use CSS classes when per-widget identity is not needed"],"exampleFix":"# before\nfor name in [\"a\", \"a\"]:\n    yield Static(name, id=name)  # DuplicateIds\n\n# after\nfor i, name in enumerate([\"a\", \"a\"]):\n    yield Static(name, id=f\"item-{i}-{name}\")","handlingStrategy":"validation","validationCode":"def ids_are_unique(parent) -> bool:\n    ids = [c.id for c in parent.children if c.id is not None]\n    return len(ids) == len(set(ids))\n\n# also check the incoming widget before mounting:\ndef can_mount(parent, widget) -> bool:\n    return widget.id is None or widget.id not in {c.id for c in parent.children}","typeGuard":null,"tryCatchPattern":"from textual._node_list import DuplicateIds\ntry:\n    await self.mount(widget)\nexcept DuplicateIds as e:\n    widget.id = f\"{widget.id}-{next(counter)}\"  # or skip/log","preventionTips":["Derive ids from guaranteed-unique keys (primary keys, uuids)","Scope queries with parent.query_one('#id') to reason about sibling uniqueness","Omit ids when uniqueness is hard to guarantee; use classes instead"],"tags":["textual","widget","duplicate-id","mount"],"backgroundTag":"duplicate-element-id","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}