Textualize/textual · error · OnDecoratorError
The attribute {attribute!r} can't be matched; have you added
Error message
The attribute {attribute!r} can't be matched; have you added it to {message_type.__name__}.ALLOW_SELECTOR_MATCH? What it means
Beyond control, the @on decorator can match on named message attributes such as Input.Changed.key or Select.Changed.value by listing them as keyword arguments. Each attribute must be declared in the message class's ALLOW_SELECTOR_MATCH set (a frozenset of legal matchable field names). Passing any keyword not present in that set raises OnDecoratorError immediately when the module is imported, with a message that names both the bad attribute and the class whose ALLOW_SELECTOR_MATCH would need updating.
Source
Thrown at src/textual/_on.py:73
matches the widget from the `control` attribute of the message.
**kwargs: Additional selectors for other attributes of the message.
"""
selectors: dict[str, str] = {}
if selector is not None:
selectors["control"] = selector
if kwargs:
selectors.update(kwargs)
parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}
for attribute, css_selector in selectors.items():
if attribute == "control":
if message_type.control == Message.control:
raise OnDecoratorError(
"The message class must have a 'control' to match with the on decorator"
)
elif attribute not in message_type.ALLOW_SELECTOR_MATCH:
raise OnDecoratorError(
f"The attribute {attribute!r} can't be matched; have you added it to "
+ f"{message_type.__name__}.ALLOW_SELECTOR_MATCH?"
)
try:
parsed_selectors[attribute] = parse_selectors(css_selector)
except TokenError:
raise OnDecoratorError(
f"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors"
) from None
def decorator(method: DecoratedType) -> DecoratedType:
"""Store message and selector in function attribute, return callable unaltered."""
if not hasattr(method, "_textual_on"):
setattr(method, "_textual_on", [])
getattr(method, "_textual_on").append((message_type, parsed_selectors))
return methodView on GitHub (pinned to 06dbeef4bb)
Solutions
- Check the message class's ALLOW_SELECTOR_MATCH and use only attributes listed there
- For custom messages, add ALLOW_SELECTOR_MATCH = {"your_field"} (inheriting or extending the parent's set) and then use @on(MyMsg, your_field="...")
- If you need matching on an unsupported field, do the filtering manually inside the handler instead of in the decorator
- Pass the plain CSS selector positionally for the default control matching, and attribute selectors only as valid keywords
Example fix
# before
class Priority(Message):
def __init__(self, level: str) -> None:
super().__init__()
self.level = level
@on(Priority, level="high") # OnDecoratorError
# after
class Priority(Message):
ALLOW_SELECTOR_MATCH = {"level"}
def __init__(self, level: str) -> None:
super().__init__()
self.level = level
@on(Priority, level="high") Defensive patterns
Strategy: validation
Validate before calling
def selectors_allowed(msg_type, kwargs: dict) -> bool:
allowed = set(getattr(msg_type, "ALLOW_SELECTOR_MATCH", set()))
return all(k in allowed or k == "control" for k in kwargs) Type guard
def has_allow_list(msg_type: type) -> bool:
return hasattr(msg_type, "ALLOW_SELECTOR_MATCH") Try / catch
validate kwargs against ALLOW_SELECTOR_MATCH in a unit test importing the module; decorator errors surface at import time
Prevention
- Read ALLOW_SELECTOR_MATCH on the message class before adding keyword selectors
- Declare ALLOW_SELECTOR_MATCH on custom messages
- Rename/update selectors when upgrading Textual versions; check the changelog
When it happens
Trigger: Writing @on(Input.Submitted, key="ctrl+s") when Input.Submitted.ALLOW_SELECTOR_MATCH does not include "key"; matching on arbitrary instance attributes of a message that were never allow-listed; using a custom message class and forgetting to add its matchable fields to ALLOW_SELECTOR_MATCH; passing the same selector string positionally instead of as the attribute keyword so it is treated as an attribute name.
Common situations: Assuming any message attribute is matchable; version drift where an attribute was renamed or removed from ALLOW_SELECTOR_MATCH in a Textual upgrade; copy-pasting a decorator from a similar message class (e.g. Changed vs Submitted) that allows different fields.
Related errors
- The message class must have a 'control' to match with the on
- No handler for {event_name!r}
- Multiple handlers for key press {key_name!r}.\nWe found both
- Unable to parse selector {css_selector!r} for {attribute}; c
- Can not create a worker from a non-async function unless `th
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/0a72c240b3fcd24a.
Report an issue: GitHub.