Textualize/textual · error · DuplicateKeyHandlers
Multiple handlers for key press {key_name!r}.\nWe found both
Error message
Multiple handlers for key press {key_name!r}.\nWe found both {first_handler!r} and {second_handler!r}, and didn't know which to call.\nConsider combining them into a single handler. What it means
When a key is pressed, Textual's dispatch_key scans the widget for handler methods (key_<key>, key_<unicode>) and also checks the декорated @on(Key) style handlers via metadata. If it finds two candidate handlers for the same key, it refuses to guess and raises DuplicateKeyHandlers, suggesting you merge them.
Source
Thrown at src/textual/_dispatch_key.py:43
Raises:
DuplicateKeyHandlers: When there's more than 1 handler that could handle this key.
"""
def get_key_handler(pump: MessagePump, key: str) -> Callable | None:
"""Look for the public and private handler methods by name on self."""
return getattr(pump, f"key_{key}", None) or getattr(pump, f"_key_{key}", None)
handled = False
invoked_method = None
key_name = event.name
if not key_name:
return False
def _raise_duplicate_key_handlers_error(
key_name: str, first_handler: str, second_handler: str
) -> None:
"""Raise exception for case where user presses a key and there are multiple candidate key handler methods for it."""
raise DuplicateKeyHandlers(
f"Multiple handlers for key press {key_name!r}.\n"
f"We found both {first_handler!r} and {second_handler!r}, "
f"and didn't know which to call.\n"
f"Consider combining them into a single handler.",
)
try:
screen = node.screen
except Exception:
screen = None
for key_method_name in event.name_aliases:
if (key_method := get_key_handler(node, key_method_name)) is not None:
if invoked_method:
_raise_duplicate_key_handlers_error(
key_name, invoked_method.__name__, key_method.__name__
)
# If key handlers return False, then they are not considered handled
# This allows key handlers to do some conditional logicView on GitHub (pinned to 06dbeef4bb)
Solutions
- Delete or rename one of the duplicate handlers so a single method handles the key
- If migrating to @on decorators, remove the old key_<name> method
- Move key handling to a parent/child level instead of duplicating on the same widget
- Merge both handlers' logic into one method
Example fix
# before
def key_a(self) -> None:
self.add_note('a')
@on(Key)
def key_handler(self, event: Key) -> None:
if event.key == 'a':
self.add_note('a') # duplicate
# after
@on(Key)
def key_handler(self, event: Key) -> None:
if event.key == 'a':
self.add_note('a') Defensive patterns
Strategy: validation
Validate before calling
import inspect
def handler_names(widget, key: str) -> list[str]:
names = []
for name, member in inspect.getmembers(type(widget), inspect.isfunction):
if name == f'key_{key}':
names.append(name)
for meta in getattr(member, '__textual_on', []):
if getattr(meta[1] if isinstance(meta, tuple) else meta, 'key', None) == key:
names.append(name)
return names
# assert len(handler_names(self, 'a')) <= 1 Type guard
def has_single_key_handler(widget, key: str) -> bool:
return len(handler_names(widget, key)) == 1 Try / catch
from textual._dispatch_key import DuplicateKeyHandlers
try:
dispatch_key(widget, key_event)
except DuplicateKeyHandlers:
widget.notify('ambiguous key handling; check logs') Prevention
- Use either key_ methods or @on decorators for a given key, never both
- When overriding key handlers in subclasses, check the base class first
- Search your widget classes for duplicate key_<name> definitions during code review
When it happens
Trigger: Defining both key_a(self) and an @on(Key('a')) handler on the same widget, or two decorated handlers matching the same key event, then pressing that key. Also occurs when a subclass inherits a key_x method and adds another handler for the same key.
Common situations: Copy-pasting a handler and changing only the decorator, migrating from key_ methods to @on decorators without removing the old method, or inheriting widgets whose base already defines a handler for the same key.
Related errors
- No handler for {event_name!r}
- Can't animate attribute {attribute!r} on {obj!r}; attribute
- Don't know how to animate {value!r}; Can only animate <int>,
- Can't encode {datum!r}
- must be bytes
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/a1769ea784f6d9c8.
Report an issue: GitHub.