Version

Quick search

Hover Behavior

The HoverBehavior mixin class provides hover detection and event handling for Kivy widgets. You can combine this class with other widgets to add specialized hover events (on_hover_enter, on_hover_update, on_hover_leave) and reactive hover state tracking.

For an overview of behaviors, please refer to the behaviors documentation.

Examples

Basic hover with visual feedback:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.behaviors.hover import HoverBehavior

class HoverButton(HoverBehavior, ButtonBehavior, Label):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.text = 'Hover me!'

    def on_hovered(self, instance, is_hovered):
        # Visual feedback: red when hovered, white otherwise
        self.color = [1, 0, 0, 1] if is_hovered else [1, 1, 1, 1]

    def on_hover_enter(self, motion_event):
        print(f"Mouse entered at {motion_event.pos}")

    def on_hover_leave(self, motion_event):
        print(f"Mouse left at {motion_event.pos}")

class SampleApp(App):
    def build(self):
        return HoverButton()

SampleApp().run()

Using with ScrollView and collision filtering:

from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.behaviors.hover import HoverBehavior
from kivy.uix.behaviors.motion import MotionCollideBehavior

class HoverScrollView(MotionCollideBehavior, ScrollView):
    # Filters hover events outside stencil bounds
    pass

class HoverItem(HoverBehavior, MotionCollideBehavior, Label):
    def on_hovered(self, instance, is_hovered):
        self.color = [1, 0, 0, 1] if is_hovered else [1, 1, 1, 1]

# Now only visible items receive hover events
# Items scrolled out of view are automatically ignored

Blocking hover leak-through to background widgets:

from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.behaviors.motion import MotionBlockBehavior

class OpaqueButton(MotionBlockBehavior, Button):
    # Blocks all unregistered motion events from passing through
    pass

layout = FloatLayout()
layout.add_widget(HoverScrollView())  # Background
layout.add_widget(OpaqueButton())     # Foreground
# Hovering over button won't trigger ScrollView hover

Configuring hover update behavior:

from kivy.uix.behaviors.hover import HoverBehavior

# Disable hover re-dispatching for stationary mouse
HoverBehavior.set_hover_update_interval(-1)

# Re-dispatch once after 0.1s (max 10 updates per second)
HoverBehavior.set_hover_update_interval(0.1)

# Default: re-dispatch once after 1/30s (max 30 updates per second)
HoverBehavior.set_hover_update_interval(1/30)

See HoverBehavior for details.

class kivy.uix.behaviors.hover.HoverBehavior(**kwargs)[source]

Bases: builtins.object

Mixin to add hover detection and event handling to any Kivy widget.

This mixin enables widgets to respond to mouse hover events with three specialized events: on_hover_enter(), on_hover_update(), and on_hover_leave(). It also provides a reactive hovered property.

Hover State Management

Tracks active hover events internally. Each hover event gets a unique ID (supports multi-touch hover). When a hover enters widget bounds, it’s tracked. When it leaves, it’s removed. The hovered property is True when any hover events are active.

Event Propagation Modes

  • “default”: Standard behavior. Events go to children first. If no child accepts, the event is offered to this widget.

  • “all”: Forces dispatch to both children AND this widget, regardless of whether children accept. Use when parent needs to track hovers even while children handle them (e.g., container with hover effects).

  • “self”: Skips children entirely. This widget captures all hover events within its bounds. Use for widgets that should be “opaque” to hover (e.g., popup overlays).

Static Hover Re-dispatching

By default, hover events are re-dispatched once when the mouse is stationary AND the time since the last dispatch exceeds 1/30s. This enables proper hover state tracking when content moves under a stationary cursor (e.g., scrolling with mousewheel).

All three hover events (enter/update/leave) are affected:

Configure globally before creating widgets:

# Disable re-dispatching entirely
HoverBehavior.set_hover_update_interval(-1)

# Re-dispatch once after 0.1s (max 10 updates per second)
HoverBehavior.set_hover_update_interval(0.1)

# Default: re-dispatch once after 1/30s (max 30 updates per second)
HoverBehavior.set_hover_update_interval(1/30)

Combining with Motion Filters

For most use cases, combine HoverBehavior with motion event filters from motion:

Example - ScrollView with Collision Filter:

from kivy.uix.behaviors.motion import MotionCollideBehavior

class HoverScrollView(MotionCollideBehavior, ScrollView):
    # Filters hover outside stencil bounds
    pass

class HoverItem(HoverBehavior, MotionCollideBehavior, Label):
    # Only receives hover when visible
    pass

Example - Blocking Hover Leak-Through:

from kivy.uix.behaviors.motion import MotionBlockBehavior

class OpaqueButton(MotionBlockBehavior, Button):
    # Blocks all unregistered motion events
    pass

layout = FloatLayout()
layout.add_widget(HoverScrollView())  # Background
layout.add_widget(OpaqueButton())     # Foreground
# Hovering button won't trigger ScrollView hover

Events

on_hover_enter()

First collision with widget bounds

on_hover_update()

Hover moved within bounds

on_hover_leave()

Hover left widget bounds

Internal State

For subclassing: _hover_ids (dict) maps active hover UIDs to positions {uid: (x, y)}. Updated by hover events. Empty when not hovered.

classmethod get_hover_update_interval()[source]

Get the current hover re-dispatch interval.

Returns:

Current interval in seconds, or negative if disabled

Example:

interval = HoverBehavior.get_hover_update_interval()
if interval < 0:
    print("Hover re-dispatching disabled")
else:
    print(f"Re-dispatches every {interval:.3f} seconds")
hover_mode

Controls how hover events propagate through the widget tree.

Modes:

  • “default”: Standard propagation. Children first, then self if not accepted. Use this for most widgets.

  • “all”: Force dispatch to both children AND self. Use when parent needs to track hovers even when children handle them (e.g., container with background hover effects).

  • “self”: Skip children entirely. Capture all hovers within bounds. Use for modal overlays or widgets that should be “opaque” to hover events.

hover_mode is an OptionProperty and defaults to “default”.

hovered

Read-only boolean indicating if widget is currently hovered.

hovered is an AliasProperty and defaults to False.

on_hover_enter(motion_event)[source]

Called when hover first enters widget bounds.

Override this method to respond to hover enter events.

Parameters:

motion_eventMotionEvent

on_hover_leave(motion_event)[source]

Called when hover exits widget bounds.

Override this method to respond to hover leave events.

Parameters:

motion_eventMotionEvent

on_hover_update(motion_event)[source]

Called when hover moves within widget bounds.

If the mouse doesn’t move AND enough time has passed since the last dispatch, this is called once.

Disable re-dispatching via set_hover_update_interval(-1) if this behavior is not needed.

Parameters:

motion_eventMotionEvent

on_motion(event_type: str, motion_event) bool[source]

Handle incoming motion events, filtering for hover events.

Main entry point for motion events. Filters for hover events and handles them according to hover_mode. Non-hover events pass to parent.

Parameters:
  • event_type – Type of motion event (“begin”, “update”, “end”)

  • motion_eventMotionEvent instance

Returns:

True if event was handled

classmethod set_hover_update_interval(interval)[source]

Configure hover re-dispatching interval globally.

Controls the minimum time between hover re-dispatches when the mouse is stationary. Re-dispatches once when:

  1. Mouse hasn’t moved, AND

  2. Time since last dispatch exceeds interval

This affects all three hover events (enter/update/leave) and is essential for scenarios where content moves under a static cursor (e.g., scrolling with mousewheel).

When enabled, the system re-checks which widgets should receive hover events, allowing proper enter/leave events as widgets scroll in/out of view.

This configuration applies to all HoverBehavior widgets and can be called before any widgets are instantiated.

Parameters:

interval – Seconds between re-dispatches. Use negative value to disable re-dispatching entirely

Example:

# Disable re-dispatching (no scroll hover updates)
HoverBehavior.set_hover_update_interval(-1)

# Re-dispatch once after 0.1s (max 10 updates per second)
HoverBehavior.set_hover_update_interval(0.1)

# Default: re-dispatch once after 1/30s (max 30 updates per second)
HoverBehavior.set_hover_update_interval(1/30)

Note

When disabled (negative interval), hover events only fire when the mouse actually moves. Content scrolling under a static cursor will not trigger enter/update/leave events.