Textualize/textual · error · ValueError
Node 'id' attribute may not be changed once set (current id=
Error message
Node 'id' attribute may not be changed once set (current id={self._id!r}) What it means
A DOMNode's id is immutable once set: the id setter raises ValueError if you try to assign a new id after one is already assigned. Textual relies on stable ids for querying and CSS matching.
Source
Thrown at src/textual/dom.py:827
@property
def id(self) -> str | None:
"""The ID of this node, or None if the node has no ID."""
return self._id
@id.setter
def id(self, new_id: str) -> str:
"""Sets the ID (may only be done once).
Args:
new_id: ID for this node.
Raises:
ValueError: If the ID has already been set.
"""
check_identifiers("id", new_id)
self._nodes.updated()
if self._id is not None:
raise ValueError(
f"Node 'id' attribute may not be changed once set (current id={self._id!r})"
)
self._id = new_id
return new_id
@property
def name(self) -> str | None:
"""The name of the node."""
return self._name
@property
def css_identifier(self) -> str:
"""A CSS selector that identifies this DOM node."""
tokens = [self.__class__.__name__]
if self.id is not None:
tokens.append(f"#{self.id}")
return "".join(tokens)
View on GitHub (pinned to 06dbeef4bb)
Solutions
- Set the id exactly once — preferably via the constructor: Widget(id='name')
- If you need a mutable label, use a custom attribute or the widget's content (e.g. label/update) instead of id
- Create a fresh widget instance rather than re-id'ing an existing one when data changes
Example fix
# before widget.id = 'header' # ... widget.id = 'header2' # ValueError # after widget = Widget(id='header2') # recreate or set only once at construction
Defensive patterns
Strategy: validation
Validate before calling
def set_id_once(widget, new_id: str):
if widget.id is not None:
raise ValueError(f'id already set to {widget.id!r}')
widget.id = new_id Type guard
def can_set_id(widget) -> bool:
return widget.id is None Try / catch
try:
widget.id = 'x'
except ValueError:
widget = widget.clone_with_id('x') if hasattr(widget, 'clone_with_id') else Widget(id='x') Prevention
- Set ids at construction only: Widget(id='name')
- Never re-id existing instances; recreate instead
- Use distinct attributes for mutable labels
When it happens
Trigger: Setting widget.id = 'a' and later widget.id = 'b'; passing id= to the constructor and then assigning widget.id again; reusing a widget instance and trying to relabel it.
Common situations: Recycling widget instances in a list/table that gets new data; programmatically assigning ids in a loop where a variable collision reassigns; refactoring from constructor id to property assignment and doing both.
Related errors
- No nodes match {self!r} on {self.node!r}
- No nodes match {self!r} on dom{self.node!r}
- {name!r} is an invalid {description}; identifiers must conta
- A Reactive class is required; for example: MyApp.theme
- Unable to bind non-reactive attribute {name!r} on {self}
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/622ec02f1c433eb0.
Report an issue: GitHub.