Textualize/textual · error · AttributeError
Can't animate attribute {attribute!r} on {obj!r}; attribute
Error message
Can't animate attribute {attribute!r} on {obj!r}; attribute does not exist What it means
Textual's animator refuses to animate an attribute that does not exist on the target object. Before creating the animation, _animate checks hasattr(obj, attribute) and raises AttributeError immediately, because there is no starting value to blend from.
Source
Thrown at src/textual/_animator.py:380
easing: EasingFunction | str = DEFAULT_EASING,
on_complete: CallbackType | None = None,
level: AnimationLevel = "full",
) -> None:
"""Animate an attribute to a new value.
Args:
obj: The object containing the attribute.
attribute: The name of the attribute.
value: The destination value of the attribute.
final_value: The final value, or ellipsis if it is the same as ``value``.
duration: The duration of the animation, or ``None`` to use speed.
speed: The speed of the animation.
easing: An easing function.
on_complete: Callback to run after the animation completes.
level: Minimum level required for the animation to take place (inclusive).
"""
if not hasattr(obj, attribute):
raise AttributeError(
f"Can't animate attribute {attribute!r} on {obj!r}; attribute does not exist"
)
assert (duration is not None and speed is None) or (
duration is None and speed is not None
), "An Animation should have a duration OR a speed"
# If an animation is already scheduled for this attribute, unschedule it.
animation_key = (id(obj), attribute)
try:
del self._scheduled[animation_key]
except KeyError:
pass
if final_value is ...:
final_value = value
start_time = self._get_time()
easing_function = EASING[easing] if isinstance(easing, str) else easingView on GitHub (pinned to 06dbeef4bb)
Solutions
- Fix the attribute name spelling to match a real attribute of the object
- Set the attribute on the object before animating (e.g. self.my_val = 0 in __init__)
- Animate supported widget attributes such as 'offset', 'size', 'opacity' (on styles) via widget.animate with correct target
- Pass the styles object when animating CSS properties, e.g. widget.animate('opacity', 0.5, on=widget.styles)
Example fix
# before
widget.animate("opactiy", 0.0) # typo -> AttributeError
# after
widget.styles.animate("opacity", 0.0) Defensive patterns
Strategy: validation
Validate before calling
if not hasattr(widget, attr) and not hasattr(widget.styles, attr):
raise ValueError(f'cannot animate {attr!r}')
widget.animate(attr, value) Type guard
def is_animatable_target(obj: object, attribute: str) -> bool:
return hasattr(obj, attribute) or hasattr(getattr(obj, 'styles', None), attribute) Try / catch
try:
widget.animate(attr, value)
except AttributeError:
# fall back to instant set
setattr(widget, attr, value) Prevention
- Initialize attributes you plan to animate in __init__
- Animate style properties via widget.styles.animate('opacity', ...)
- Double-check attribute spelling against the widget API
When it happens
Trigger: Calling widget.animate(...) or animator.animate(obj, ...) with an attribute name that is misspelled or not set on the object yet, e.g. `widget.animate('opactiy', 1.0)` or animating 'color' on an object that has no color attribute. Also animating an attribute that is only set inside styles, not as a Python attribute.
Common situations: Typos in the attribute name; assuming CSS-style properties (e.g. 'opacity' from stylesheet) exist as Python attributes of the widget; animating a custom attribute before initializing it in __init__.
Related errors
- Don't know how to animate {value!r}; Can only animate <int>,
- {duration!r} is not a valid duration.
- module {__name__!r} has no attribute {name!r}
- Can't encode {datum!r}
- must be bytes
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/0b36bc0c4256302c.
Report an issue: GitHub.