Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt
Use this file to discover all available pages before exploring further.
Kael offers two complementary animation systems: implicit transitions that activate automatically whenever a style property changes, and explicit animations you compose with the Animation struct and attach to any element. Both systems run on the GPU render thread, so they stay smooth even while your application logic is busy. For self-playing vector animations, Kael also integrates Lottie through the lottie() element.
Implicit style transitions
You do not need to write any animation code to get smooth transitions between states. Whenever a div() or other styled element changes one of the following properties between renders, Kael interpolates the value over 150 ms with an ease-out curve automatically:
opacity
rotate
scale / scale_xy
transform_origin
background / bg
border_color
// These properties animate to their new values automatically
div()
.opacity(if hovered { 1.0 } else { 0.6 })
.scale(if pressed { 0.97 } else { 1.0 })
.bg(if active { theme.colors.primary } else { theme.colors.surface })
Implicit transitions only fire when the element retains the same element identity across renders. Use a stable .id() on elements that conditionally appear to ensure continuity.
The Animation struct
For explicit, timed animations you use Animation::new() and attach it to an element with the AnimationExt trait, which is automatically implemented for every IntoElement.
pub struct Animation { /* … */ }
impl Animation {
pub fn new(duration: Duration) -> Self
pub fn easing(self, easing: Easing) -> Self
pub fn with_easing(self, easing: impl Fn(f32) -> f32 + 'static) -> Self
pub fn delay(self, delay: Duration) -> Self
pub fn repeat(self, repeat: Repeat) -> Self
pub fn repeat_forever(self) -> Self
}
Easing functions
The Easing enum covers the most common curves as well as advanced options:
pub enum Easing {
Linear,
EaseIn, // quadratic ease-in
EaseOut, // quadratic ease-out
EaseInOut, // quadratic ease-in-out
CubicBezier(f32, f32, f32, f32), // CSS-style control points
Spring {
stiffness: f32,
damping: f32,
mass: f32,
},
Custom(Rc<dyn Fn(f32) -> f32>),
}
Use the .easing() builder for named curves, or .with_easing() for an inline closure:
use std::time::Duration;
// Named easing
let anim = Animation::new(Duration::from_millis(300))
.easing(Easing::EaseOut);
// CSS cubic-bezier equivalent of "ease"
let anim = Animation::new(Duration::from_millis(400))
.easing(Easing::CubicBezier(0.25, 0.1, 0.25, 1.0));
// Custom closure
let anim = Animation::new(Duration::from_millis(500))
.with_easing(|t| t * t * (3.0 - 2.0 * t)); // smoothstep
Spring animations
Kael implements a damped spring curve natively. Tune stiffness, damping, and mass to match the feel of your interface:
let bouncy = Animation::new(Duration::from_millis(600))
.easing(Easing::Spring {
stiffness: 300.0,
damping: 20.0,
mass: 1.0,
});
Repeat behavior
pub enum Repeat {
Once, // play once and stop
Count(u32), // play N times and stop
Forever, // loop indefinitely
}
// Play three times
Animation::new(Duration::from_millis(200))
.repeat(Repeat::Count(3))
// Loop forever
Animation::new(Duration::from_secs(1))
.repeat_forever()
Attaching animations to elements
The AnimationExt trait adds .with_animation(), .with_animations(), and .with_keyframes() to every element.
.with_animation()
The animator closure receives a f32 delta in the range 0.0..=1.0 and returns the modified element:
div()
.w_32()
.h_32()
.bg(theme.colors.primary)
.with_animation(
"fade-in",
Animation::new(Duration::from_millis(300)).easing(Easing::EaseOut),
|el, delta| el.opacity(delta),
)
.with_animations() — multiple sequential animations
When you have a list of Animation values, the animator also receives the zero-based index of the currently active animation:
div()
.with_animations(
"entrance",
vec![
Animation::new(Duration::from_millis(200)).easing(Easing::EaseOut),
Animation::new(Duration::from_millis(200))
.easing(Easing::EaseIn)
.delay(Duration::from_millis(200)),
],
|el, index, delta| match index {
0 => el.opacity(delta),
1 => el.scale(0.9 + 0.1 * delta),
_ => el,
},
)
Cancellable animations
Use .with_cancellable_animation() when you need to stop the animation early and snap it to its final state:
let (animated, handle) = div()
.with_cancellable_animation(
"spinner",
Animation::new(Duration::from_secs(1)).repeat_forever(),
|el, delta| el.rotate(delta * 360.0),
);
// Later, when loading is complete:
handle.cancel();
Keyframe animations
keyframes() lets you declaratively describe style targets at normalized time offsets (0.0 = start, 1.0 = end). Keyframes support opacity, scale, scale_xy, and rotate.
pub fn keyframes() -> Keyframes
impl Keyframes {
pub fn at(self, progress: f32, build: impl FnOnce(StyledKeyframe) -> StyledKeyframe) -> Self
}
pub struct StyledKeyframe { /* … */ }
impl StyledKeyframe {
pub fn opacity(self, opacity: f32) -> Self
pub fn scale(self, factor: f32) -> Self
pub fn scale_xy(self, x: f32, y: f32) -> Self
pub fn rotate(self, degrees: f32) -> Self
}
Use .with_keyframes() or the shorthand .animation() to bind keyframes to an Animation:
div()
.w_16()
.h_16()
.bg(theme.colors.primary)
.rounded_full()
.with_keyframes(
"pulse",
keyframes()
.at(0.0, |k| k.scale(1.0).opacity(1.0))
.at(0.5, |k| k.scale(1.15).opacity(0.7))
.at(1.0, |k| k.scale(1.0).opacity(1.0)),
Animation::new(Duration::from_secs(2)).repeat_forever(),
)
Animation sequences
AnimationSequence chains multiple Animation values end-to-end, with optional overlap:
pub struct AnimationSequence { /* … */ }
impl AnimationSequence {
pub fn new() -> Self
pub fn then(self, animation: Animation) -> Self
pub fn then_for(self, duration: Duration) -> Self
pub fn with_overlap(self, overlap: Duration) -> Self
pub fn into_animations(self) -> Vec<Animation>
pub fn animations(&self) -> &[Animation]
}
let sequence = AnimationSequence::new()
.then(Animation::new(Duration::from_millis(200)).easing(Easing::EaseOut))
.then(Animation::new(Duration::from_millis(150)).easing(Easing::EaseIn))
.with_overlap(Duration::from_millis(50));
div()
.with_animation_sequence(
"slide-bounce",
sequence,
|el, index, delta| match index {
0 => el.opacity(delta),
1 => el.scale(1.0 - 0.05 * (1.0 - delta)),
_ => el,
},
)
Lottie animations
For pre-authored vector animations, use the lottie() element. It loads .lottie or Bodymovin JSON files off-thread and renders them through Kael’s GPU atlas.
lottie("assets/success.lottie")
.autoplay()
.loop_mode(LoopMode::Once)
.w_48()
.h_48()
// Ping-pong loop
lottie("assets/idle.lottie")
.autoplay()
.ping_pong()
.w_32()
.h_32()
See the Elements guide for the full lottie() API including loading and fallback callbacks.