Skip to content
Feeding a clanker? Grab this page as raw .md

HoverSlider API#

HoverSlider treats hovering as an input channel of its own: hover_value follows the pointer while value stays parked where you last clicked, so a cell can show you what a setting would do before you commit to it. Pass start/stop/step for a linear range or steps for a list of discrete values, and use sync_throttle_ms to cap how often the hover stream reruns downstream cells.

See also: PlaySlider for stepping through a range on a timer, CircularSlider for the same range laid out on a dial, and TangleSlider for a draggable number that lives inside a sentence.

Bases: AnyWidget

Horizontal slider that reports both a committed value and a live hover value.

Hovering the track is an input channel of its own: hover_value follows the pointer while value stays parked where you last clicked. That lets a notebook preview a result before you commit to it. Click (or drag the puck) to move value; when the pointer leaves the track hover_value falls back to value, so it is never None.

Mirrors mo.ui.slider semantics: pass start/stop/step for a linear range, or steps for a list of discrete values (the two are mutually exclusive). Numeric types are preserved -- steps=[1, 2, 3] hands back an int, steps=[1, 2.5, 4] hands back floats.

Note

Hover fires a lot. Because mo.ui.anywidget reruns dependent cells on every synced trait change, sync_throttle_ms is the knob that decides how hard this widget hits your notebook: the default of 100ms caps it at roughly 10 reruns per second while the pointer sweeps. Raise it if downstream cells are expensive; set it to 0 to sync every single pointer move.

Examples:

import marimo as mo
from wigglystuff import HoverSlider

slider = mo.ui.anywidget(HoverSlider(start=0, stop=100, step=1, value=42))
slider
# `hover_value` previews, `value` is what the user actually committed.
mo.md(f"previewing {slider.value['hover_value']}, committed {slider.value['value']}")

Create a HoverSlider.

Parameters:

Name Type Description Default
start Optional[float]

Lower bound of the range. Defaults to 0.

None
stop Optional[float]

Upper bound of the range. Defaults to 100.

None
step Optional[float]

Snap increment (must be > 0). Defaults to 1.

None
steps Optional[Sequence[float]]

List of discrete values to snap to, laid out evenly across the track regardless of spacing. Mutually exclusive with start/stop/step. Needs at least two entries.

None
value Optional[float]

Initial committed value; defaults to start (or steps[0]). Snapped into range.

None
sync_throttle_ms int

Cap on how often hover/drag updates reach Python, in milliseconds. 0 syncs on every pointer move.

100
show_value bool

Render the committed and hovered values as text below the track.

True
label str

Optional text label shown above the track. Empty string hides it.

''
color str

Optional CSS color (e.g. "#ef4444", "tomato") for the fill, puck border, and hover marker. Empty string uses the theme default.

''
width int

Widget width in pixels.

400
**kwargs Any

Forwarded to anywidget.AnyWidget.

{}
Source code in wigglystuff/hover_slider.py
def __init__(
    self,
    start: Optional[float] = None,
    stop: Optional[float] = None,
    step: Optional[float] = None,
    steps: Optional[Sequence[float]] = None,
    value: Optional[float] = None,
    sync_throttle_ms: int = 100,
    show_value: bool = True,
    label: str = "",
    color: str = "",
    width: int = 400,
    **kwargs: Any,
) -> None:
    """Create a HoverSlider.

    Args:
        start: Lower bound of the range. Defaults to ``0``.
        stop: Upper bound of the range. Defaults to ``100``.
        step: Snap increment (must be > 0). Defaults to ``1``.
        steps: List of discrete values to snap to, laid out evenly across the
            track regardless of spacing. Mutually exclusive with
            ``start``/``stop``/``step``. Needs at least two entries.
        value: Initial committed value; defaults to ``start`` (or ``steps[0]``).
            Snapped into range.
        sync_throttle_ms: Cap on how often hover/drag updates reach Python, in
            milliseconds. ``0`` syncs on every pointer move.
        show_value: Render the committed and hovered values as text below the track.
        label: Optional text label shown above the track. Empty string hides it.
        color: Optional CSS color (e.g. ``"#ef4444"``, ``"tomato"``) for the fill,
            puck border, and hover marker. Empty string uses the theme default.
        width: Widget width in pixels.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.
    """
    if steps is not None and (
        start is not None or stop is not None or step is not None
    ):
        raise ValueError(
            "Invalid arguments: `steps` is mutually exclusive with "
            "`start`, `stop`, and `step`."
        )

    if steps is not None:
        steps = _as_list(steps)
        if not all(_is_number(s) for s in steps):
            raise TypeError("Invalid steps: steps must be a sequence of numbers.")
        if len(steps) < 2:
            raise ValueError("Must pass at least two steps.")
        dtype = _infer_dtype(list(steps) + [value])
        steps = [_cast(s, dtype) for s in steps]
        value = steps[0] if value is None else _nearest(steps, value)
        start, stop, step = steps[0], steps[-1], None
    else:
        start = 0 if start is None else start
        stop = 100 if stop is None else stop
        step = 1 if step is None else step
        if not all(_is_number(x) for x in (start, stop, step)):
            raise TypeError("start, stop and step must be numbers.")
        if value is not None and not _is_number(value):
            raise TypeError("value must be a number.")
        if start >= stop:
            raise ValueError("start must be less than stop.")
        if step <= 0:
            raise ValueError("step must be positive.")
        dtype = _infer_dtype([start, stop, step, value])
        start, stop, step = (
            _cast(start, dtype),
            _cast(stop, dtype),
            _cast(step, dtype),
        )
        value = _snap_linear(
            start if value is None else value, start, stop, step, dtype
        )
        steps = []

    # Pass value *and* hover_value explicitly: traitlets skips cross-validation
    # for traits you leave at their class default.
    super().__init__(
        start=start,
        stop=stop,
        step=step,
        steps=steps,
        value=value,
        hover_value=value,
        hovering=False,
        sync_throttle_ms=sync_throttle_ms,
        show_value=show_value,
        label=label,
        color=color,
        width=width,
        **kwargs,
    )
    self.observe(self._mirror_hover_value, names="value")

Synced traitlets#

Traitlet Type Notes
value int \| float Committed value. Moves on click, drag, and arrow keys — never on plain hover.
hover_value int \| float Value under the pointer. Falls back to value when the pointer leaves, so it is never None.
hovering bool Whether the pointer is on the track, i.e. whether hover_value is live.
start int \| float Lower bound. In steps mode this is steps[0].
stop int \| float Upper bound. In steps mode this is steps[-1].
step int \| float \| None Snap increment. None in steps mode.
steps list[int \| float] Discrete values, laid out evenly across the track. Empty means linear mode.
sync_throttle_ms int Cap on how often hover updates reach Python. 0 syncs every pointer move.
show_value bool Render the committed and hovered values below the track.
label str Text above the track. Empty string hides it.
color str CSS color for the fill, puck border, and hover marker. Empty uses the theme default.
width int Widget width in pixels.