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

  1. Set the id exactly once — preferably via the constructor: Widget(id='name')
  2. If you need a mutable label, use a custom attribute or the widget's content (e.g. label/update) instead of id
  3. 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

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


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