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 logic

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Delete or rename one of the duplicate handlers so a single method handles the key
  2. If migrating to @on decorators, remove the old key_<name> method
  3. Move key handling to a parent/child level instead of duplicating on the same widget
  4. 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

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


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