Declarative animations powered by Motion (motion.dev)
Predefined animation presets for common enter effects:
Fades in from transparent
Slides up while fading in
Slides down while fading in
Scales up from 0.9x
Bounces in from above
Define your own animation parameters with full control:
x:-50, opacity:0
x:50, opacity:0
rotate:-15, scale:0.8
delay:500ms
Spring presets use cubic-bezier approximations. Bouncy has overshoot, slow is gradual:
Soft and smooth
Playful overshoot
Snappy response
Relaxed timing
Hover over these elements to see the animation:
Grows on hover
Rises on hover
Tilts on hover
Click and hold these buttons to see press effects:
Scroll down to see these elements animate into view. Scroll away and back to see them again!
Animates when scrolled into view
Slides up from below
Scales up into view
Slides in from the left
Slides in from the right
Elements that animate based on scroll position (watch as you scroll this section):
Moves slower (y: 0 to -50)
Moves faster (y: 0 to 50)
Scales up as you scroll (0.8 to 1.0)
Fades in as you scroll (0.3 to 1.0)
Rotates as you scroll (-10 to 10 deg)
Use the delay parameter for staggered timing. The last card demonstrates data-on:motion-complete event handling.
delay: 0ms
delay: 200ms
delay: 400ms
delay: 600ms + on_complete
The visibility() helper provides a clean, single-attribute API for animated show/hide. It watches a signal and handles both enter and exit animations automatically.
This content smoothly animates in and out using the visibility() helper. The layout shift is intentional for accordion-style components.
# Accordion with visibility():
(show := Signal('show', False)),
Button('Toggle', data_on_click=show.toggle()),
Div(
'Expandable content here...',
data_motion=visibility(
signal=show,
enter=enter(y=-20, opacity=0, duration=400),
exit_=exit_(y=-10, opacity=0, duration=250),
),
) Use repeat() for looping animations:
Repeats 3 times
Repeats forever
Use motion.animate(), motion.set() for event-driven animations:
Use motion.remove() and motion.replace() for SSE-driven DOM changes with exit animations. The element plays its exit animation before being removed or replaced.
Use motion_replace() SSE helper for animated replacements. Exit animation plays first, then new content fades in:
Original content
# SSE endpoint with exit animation:
from starhtml.plugins import motion_replace
@rt('/update')
@sse
def update(req):
yield motion_replace(
"#target",
Div('New content',
data_motion_exit=exit_(opacity=0))
) Chain animations with motion.sequence() using dict() syntax for keyframes and options. Click Reset first to hide elements, then Run Sequence to see the staggered animation:
Animations triggered by element resize. Drag the corner to resize and see the pulse effect:
Pulses when resized (scale bounces from 1.1x to 1x)
Simple play/pause toggle with stop:
Play starts or resumes. Pause freezes at current position. Stop uses cancel() + set() to revert to original position.
Give animations a name to control them programmatically from anywhere:
Named 'hero' for remote control
# Named animations can be controlled from anywhere
motion.animate("#hero", scale=1.05, repeat="infinite", name="hero")
motion.pause("hero") # Pause by name
motion.play("hero") # Resume by name
motion.cancel("hero") + motion.set("#hero", scale=1) # Stop and reset Listen to animation lifecycle events. Click 'Start Slide' then quickly 'Stop' to see the cancel event:
Slides across over 3 seconds
data_on_motion_start=event_log.set("Started!")
data_on_motion_complete=event_log.set("Complete!")
data_on_motion_cancel=event_log.set("Cancelled!") Motion config is declarative - describes WHAT the animation looks like:
duration=300 - Animation duration in ms delay=0 - Delay before animation starts repeat=n - Repeat n times or 'infinite' stagger=100 - Stagger children by delay (ms) spring='bouncy' - Spring preset: gentle, bouncy, tight, slow data_on_motion_start - Handler for animation start event data_on_motion_complete - Handler for animation complete event data_on_motion_cancel - Handler for animation cancel event data_show=signal - Combine with Datastar for visibility motion.is_animating - Boolean: animation active motion.phase - Current phase: idle/enter/hover/tap motion.progress - Scroll progress 0-100