Skip to content

Modifiers

Functional-style modifiers to apply styling, layout, and event handling to widgets.

modifiers

focusable

focusable(enabled: bool = True, on_focus_change: Optional[FocusChangeCallback] = None, on_key: Optional[Callable[[str, int], bool]] = None, on_key_up: Optional[Callable[[str, int], bool]] = None) -> FocusableModifier

Mark the widget as focusable.

Parameters:

Name Type Description Default
enabled bool

Whether the widget is focusable.

True
on_focus_change Optional[FocusChangeCallback]

Callback invoked when focus state changes.

None
on_key Optional[Callable[[str, int], bool]]

Callback invoked as on_key(key, modifier_keys) when a key is pressed while focused. modifier_keys is a bitmask of the held modifier keys (MOD_SHIFT, MOD_CTRL, ...). Return True to stop propagation (bubbling).

None
on_key_up Optional[Callable[[str, int], bool]]

Callback invoked as on_key_up(key, modifier_keys) when a key is released while focused. Bubbles to ancestor focusables and stops on a truthy return, exactly like on_key.

None
Source code in src/nuiitivet/modifiers/focus.py
def focusable(
    enabled: bool = True,
    on_focus_change: Optional[FocusChangeCallback] = None,
    on_key: Optional[Callable[[str, int], bool]] = None,
    on_key_up: Optional[Callable[[str, int], bool]] = None,
) -> FocusableModifier:
    """
    Mark the widget as focusable.

    Args:
        enabled: Whether the widget is focusable.
        on_focus_change: Callback invoked when focus state changes.
        on_key: Callback invoked as ``on_key(key, modifier_keys)`` when a key is
                pressed while focused. ``modifier_keys`` is a bitmask of the held
                modifier keys (``MOD_SHIFT``, ``MOD_CTRL``, ...). Return True to
                stop propagation (bubbling).
        on_key_up: Callback invoked as ``on_key_up(key, modifier_keys)`` when a
                key is released while focused. Bubbles to ancestor focusables and
                stops on a truthy return, exactly like ``on_key``.
    """
    return FocusableModifier(enabled, on_focus_change, on_key, on_key_up)

on_mount

on_mount(callback: VoidCallback) -> OnMountModifier

Return a modifier that runs callback when the widget is mounted.

The callback runs right after the widget's :meth:Widget.on_mount hook and before its children are mounted. Exceptions are logged and contained.

Parameters:

Name Type Description Default
callback VoidCallback

A no-argument callable. If it is a coroutine function, it is started as a task on mount and cancelled on unmount.

required

Returns:

Name Type Description
An OnMountModifier

class:OnMountModifier to apply via widget.modifier(...).

Note

Mount is not "once per logical component". A ComposableWidget rebuild discards the built subtree and mounts freshly-created widget instances, so the callback runs again for the new instance. Use it for work tied to the widget instance's presence in the tree, not for one-time initialization of a component.

Source code in src/nuiitivet/modifiers/lifecycle.py
def on_mount(callback: VoidCallback) -> OnMountModifier:
    """Return a modifier that runs *callback* when the widget is mounted.

    The callback runs right after the widget's :meth:`Widget.on_mount` hook and
    before its children are mounted. Exceptions are logged and contained.

    Args:
        callback: A no-argument callable. If it is a coroutine function, it is
            started as a task on mount and cancelled on unmount.

    Returns:
        An :class:`OnMountModifier` to apply via ``widget.modifier(...)``.

    Note:
        Mount is *not* "once per logical component". A ``ComposableWidget``
        rebuild discards the built subtree and mounts freshly-created widget
        instances, so the callback runs again for the new instance. Use it for
        work tied to the widget instance's presence in the tree, not for
        one-time initialization of a component.
    """
    return OnMountModifier(callback=callback)

on_unmount

on_unmount(callback: VoidCallback) -> OnUnmountModifier

Return a modifier that runs callback when the widget is unmounted.

The callback runs right after the widget's :meth:Widget.on_unmount hook and before its children are unmounted. Exceptions are logged and contained.

Parameters:

Name Type Description Default
callback VoidCallback

A no-argument callable. A coroutine function is scheduled as a task, which may outlive the widget — prefer a synchronous callback for cleanup that must complete.

required

Returns:

Name Type Description
An OnUnmountModifier

class:OnUnmountModifier to apply via widget.modifier(...).

Source code in src/nuiitivet/modifiers/lifecycle.py
def on_unmount(callback: VoidCallback) -> OnUnmountModifier:
    """Return a modifier that runs *callback* when the widget is unmounted.

    The callback runs right after the widget's :meth:`Widget.on_unmount` hook
    and before its children are unmounted. Exceptions are logged and contained.

    Args:
        callback: A no-argument callable. A coroutine function is scheduled as
            a task, which may outlive the widget — prefer a synchronous
            callback for cleanup that must complete.

    Returns:
        An :class:`OnUnmountModifier` to apply via ``widget.modifier(...)``.
    """
    return OnUnmountModifier(callback=callback)

shadows

shadows(layers: ShadowLike) -> ShadowModifier

Draw a stack of shadow layers behind the widget.

Parameters:

Name Type Description Default
layers ShadowLike

A Shadow, a sequence of them ordered back to front, or None for no shadow. Material Design's elevation, for instance, is a key layer over a wider ambient one.

required

Returns:

Type Description
ShadowModifier

The modifier to apply.

Source code in src/nuiitivet/modifiers/shadow.py
def shadows(layers: ShadowLike) -> ShadowModifier:
    """Draw a stack of shadow layers behind the widget.

    Args:
        layers: A ``Shadow``, a sequence of them ordered back to front, or
            ``None`` for no shadow. Material Design's elevation, for
            instance, is a key layer over a wider ambient one.

    Returns:
        The modifier to apply.
    """
    return ShadowModifier(layers=normalize_shadows(layers))

on_size_changed

on_size_changed(callback: SizeCallback) -> OnSizeChangedModifier

Return a modifier that calls callback with the widget's measured size.

The callback receives a :class:Size - the widget's own (width, height) after layout, excluding its position. Use it to feed a size into imperative state: a ViewModel, or a plain Observable a child widget binds to::

class ResponsiveScaffold(ComposableWidget):
    def __init__(self) -> None:
        super().__init__()
        self.expanded = Observable(False)

    def _on_size(self, size: Size) -> None:
        self.expanded.value = size.width >= 700

    def build(self) -> Widget:
        rail = NavigationRail(..., expanded=self.expanded)
        return Row([rail, card], ...).modifier(on_size_changed(self._on_size))

Parameters:

Name Type Description Default
callback SizeCallback

A callable taking a :class:Size, sync or async. An async callback is scheduled as a task. Exceptions are logged and contained.

required

Returns:

Name Type Description
An OnSizeChangedModifier

class:OnSizeChangedModifier to apply via widget.modifier(...).

Note

Fires once with the first measurement, so the callback alone is enough to seed the state it drives. After that it fires only when the measured size actually changed; a widget that is re-laid-out at the same size, or merely moved, is silent.

Note

Dispatched between frames, never during layout, so the callback may safely do anything - push a route, write an Observable, replace children - and its effect lands on the frame after the measurement.

That includes the first call, which therefore arrives after the first paint. Give an Observable the value you expect at the initial size and the first report writes the same value, which de-dupes: no visible transition. Seed it differently and the widget animates once on startup.

Warning

Avoid making the callback change the measured widget's own size: that feeds back into the next measurement and can oscillate. The structurally safe pattern is to measure a widget whose size the parent imposes (Sizing.weight(...) / "wt") and let the callback change only what is inside it.

Source code in src/nuiitivet/modifiers/size_changed.py
def on_size_changed(callback: SizeCallback) -> OnSizeChangedModifier:
    """Return a modifier that calls *callback* with the widget's measured size.

    The callback receives a :class:`Size` - the widget's own ``(width, height)``
    after layout, excluding its position. Use it to feed a size into imperative
    state: a ViewModel, or a plain ``Observable`` a child widget binds to::

        class ResponsiveScaffold(ComposableWidget):
            def __init__(self) -> None:
                super().__init__()
                self.expanded = Observable(False)

            def _on_size(self, size: Size) -> None:
                self.expanded.value = size.width >= 700

            def build(self) -> Widget:
                rail = NavigationRail(..., expanded=self.expanded)
                return Row([rail, card], ...).modifier(on_size_changed(self._on_size))

    Args:
        callback: A callable taking a :class:`Size`, sync or async. An async
            callback is scheduled as a task. Exceptions are logged and contained.

    Returns:
        An :class:`OnSizeChangedModifier` to apply via ``widget.modifier(...)``.

    Note:
        **Fires once with the first measurement**, so the callback alone is
        enough to seed the state it drives. After that it fires only when the
        measured size actually changed; a widget that is re-laid-out at the same
        size, or merely moved, is silent.

    Note:
        **Dispatched between frames, never during layout**, so the callback may
        safely do anything - push a route, write an Observable, replace
        children - and its effect lands on the frame after the measurement.

        That includes the first call, which therefore arrives *after* the first
        paint. Give an Observable the value you expect at the initial size and
        the first report writes the same value, which de-dupes: no visible
        transition. Seed it differently and the widget animates once on startup.

    Warning:
        Avoid making the callback change the measured widget's *own* size: that
        feeds back into the next measurement and can oscillate. The structurally
        safe pattern is to measure a widget whose size the parent imposes
        (``Sizing.weight(...)`` / ``"wt"``) and let the callback change only what
        is *inside* it.
    """
    return OnSizeChangedModifier(callback=callback)

opacity

opacity(value: OpacityLike) -> TransformModifier

Return a modifier that applies opacity to a widget during paint.

Parameters:

Name Type Description Default
value OpacityLike

Opacity value between 0.0 (transparent) and 1.0 (opaque), or an observable providing opacity.

required
Note

Opacity is paint-only. Layout and hit-testing remain unchanged.

Source code in src/nuiitivet/modifiers/transform.py
def opacity(value: OpacityLike) -> TransformModifier:
    """Return a modifier that applies opacity to a widget during paint.

    Args:
        value: Opacity value between 0.0 (transparent) and 1.0 (opaque),
            or an observable providing opacity.

    Note:
        Opacity is paint-only. Layout and hit-testing remain unchanged.
    """
    return TransformModifier(opacity=value)

rotate

rotate(angle: AngleLike, origin: OriginLike = 'center') -> TransformModifier

Return a modifier that rotates a widget during paint.

Parameters:

Name Type Description Default
angle AngleLike

Rotation angle in degrees, or an observable providing degrees.

required
origin OriginLike

Rotation origin. Accepts any of the nine-point alignment tokens in canonical hyphen form ("center", "top-left", "top-center", "top-right", "center-left", "center-right", "bottom-left", "bottom-center", "bottom-right"); the underscore form ("top_left") is accepted as an alias. Alternatively an (x, y) tuple in local coords. Defaults to "center".

'center'
Note

Rotation is paint-only. Layout and hit-testing remain untransformed.

Source code in src/nuiitivet/modifiers/transform.py
def rotate(angle: AngleLike, origin: OriginLike = "center") -> TransformModifier:
    """Return a modifier that rotates a widget during paint.

    Args:
        angle: Rotation angle in degrees, or an observable providing degrees.
        origin: Rotation origin. Accepts any of the nine-point alignment tokens
            in canonical hyphen form ("center", "top-left", "top-center",
            "top-right", "center-left", "center-right", "bottom-left",
            "bottom-center", "bottom-right"); the underscore form ("top_left")
            is accepted as an alias. Alternatively an (x, y) tuple in local
            coords. Defaults to "center".

    Note:
        Rotation is paint-only. Layout and hit-testing remain untransformed.
    """
    return TransformModifier(rotation=angle, transform_origin=origin)

scale

scale(factor: ScaleLike, origin: OriginLike = 'center') -> TransformModifier

Return a modifier that scales a widget during paint.

Parameters:

Name Type Description Default
factor ScaleLike

Scale factor (uniform) or (sx, sy) tuple, or an observable.

required
origin OriginLike

Scale origin. Accepts any of the nine-point alignment tokens in canonical hyphen form ("center", "top-left", "top-center", "top-right", "center-left", "center-right", "bottom-left", "bottom-center", "bottom-right"); the underscore form ("top_left") is accepted as an alias. Alternatively an (x, y) tuple in local coords. Defaults to "center".

'center'
Note

Scale is paint-only. Layout and hit-testing remain untransformed.

Source code in src/nuiitivet/modifiers/transform.py
def scale(factor: ScaleLike, origin: OriginLike = "center") -> TransformModifier:
    """Return a modifier that scales a widget during paint.

    Args:
        factor: Scale factor (uniform) or (sx, sy) tuple, or an observable.
        origin: Scale origin. Accepts any of the nine-point alignment tokens
            in canonical hyphen form ("center", "top-left", "top-center",
            "top-right", "center-left", "center-right", "bottom-left",
            "bottom-center", "bottom-right"); the underscore form ("top_left")
            is accepted as an alias. Alternatively an (x, y) tuple in local
            coords. Defaults to "center".

    Note:
        Scale is paint-only. Layout and hit-testing remain untransformed.
    """
    return TransformModifier(scale=factor, transform_origin=origin)

translate

translate(offset: TranslateLike) -> TransformModifier

Return a modifier that translates a widget during paint.

Parameters:

Name Type Description Default
offset TranslateLike

Translation offset as (dx, dy) tuple, or an observable.

required
Note

Translation is paint-only. Layout and hit-testing remain untransformed.

Source code in src/nuiitivet/modifiers/transform.py
def translate(offset: TranslateLike) -> TransformModifier:
    """Return a modifier that translates a widget during paint.

    Args:
        offset: Translation offset as (dx, dy) tuple, or an observable.

    Note:
        Translation is paint-only. Layout and hit-testing remain untransformed.
    """
    return TransformModifier(translation=offset)