Skip to content

Animations

Animation in kadokit is declarative: you attach an animation configuration to a property, and every change to that property moves through it, whether it comes from state, firmware, or user interaction. There are three levels: tween easings, spring physics, and orchestrated motion groups.

Tween animations

Animate property changes with an easing curve:

arc {
    value bind="app.speed" animate="out-cubic" duration=400
}

Widgets can also declare per-property animations as standalone nodes. This is useful when several properties of the same widget animate differently:

box {
    x bind="app.offset"
    animate "x" tween-duration=300 tween-easing="out-cubic"
    animate "opa" tween-duration=180 tween-easing="linear"
}

Easing functions

linear, plus in- / out- / in-out- variants of quad, cubic, quart, quint, sine, expo, circ, and back. There are 26 curves total. out-* easings are generally best for UI: fast start, gentle stop.

Spring animations

Physics-based motion for more natural movement. Named presets:

arc {
    value bind="app.speed" spring="snappy"
}
PresetFeel
snappyQuick, minimal overshoot (default)
gentleSlow, smooth
softVery slow, no overshoot
sheetLike a bottom sheet opening
overshootStrong bounce past target
critically-dampedFastest path to target, no bounce

Or custom parameters:

box {
    transform-scale {
        bind="app.scale_var"
        spring-duration=0.3     // seconds, 0.1 to 2.0
        spring-bounce=0.2       // 0.0 (none) to 1.0 (maximum)
    }
}

Animatable properties

value (arc, slider, bar), opa, bg-opa, x, y, width, height, transform-scale, transform-rotation, and the color properties (bg-color, text-color, border-color, animated per channel). Non-animatable properties (text, booleans) change immediately even if an animation is specified.

Components with animated props

Animation configuration lives inside the component; callers just drive the value:

component "ArcGauge" {
    prop "value" type="int" default=50

    arc {
        value bind="prop.value" spring="snappy"
    }
}

ArcGauge { value bind="app.cpu_load" }

Motion groups

Motion groups orchestrate animations across many widgets at once: staggered entrances, waves, coordinated reveals. A group declares its targets, a stagger pattern, and the property tracks to run; it is triggered with the play action from any event, mount hook, or timer:

grid {
    columns 16; rows 4; cell-width 25; cell-height 25; gap 7
    on-mount { play "charge-wave" }

    motion-group "charge-wave" {
        target "children"
        // Top-to-bottom wave: each row starts 90ms after the previous
        stagger grid="16x4" row-offset=90 col-offset=90 from="first" max-active=18
        to "transform-scale" 256 from=64 tween-duration=440 tween-easing="out-cubic"
    }

    repeat count=64 as="cell" {
        box { style "cell"; on-click { play "charge-wave" } }
    }

    // Auto-replay every 4.5 seconds
    timer {
        interval 4500; repeat #true
        play "charge-wave"
    }
}

This is a complete charge-wave effect over a 16×4 grid, with 64 widgets animating in a staggered pattern and no imperative animation code.

Layout (FLIP) animations

When firmware reorders widgets (shuffle, sort), layout-aware spring transitions can animate each widget from its old position to its new one using the First-Last-Invert-Play technique, driven by the same motion engine.

Screen transitions

Transitions between screens are configured on the screen itself and are separate from property animations. See Navigation → Transitions.

How it behaves

Animated properties settle on their target automatically, and if the target changes mid-animation, motion continues from the current position and velocity. When a change cannot be animated, the property is applied directly — a bound value always reaches its final state.