Maple
⌘K
DOCUMENTATIONv2.0.17

Animations

Apply rich motion using built-in aliases like fade-in, scale-in, or spin, then customize timing and keyframe variables directly in markup.

Setup

Maple generates the necessary CSS animation properties automatically. However, built-in animations like fade-in, slide-in-up, spin, or shake require matching CSS @keyframes rules to execute. To use them, include the companion keyframes.css stylesheet in your document head before loading maple.js.

 <head>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@f12io/maple/dist/keyframes.css"
  />
  <script src="https://cdn.jsdelivr.net/npm/@f12io/maple/dist/maple.js"></script>
</head> 
The Fast Path
Use an alias for common motion: fade-in-up. Use individual utilities like animdur-900 when you need to override a property. Use anim-* when you need a custom animation shorthand: anim-scale-in_500_ease-out_300_both. Define variables like --animdur-fade-in-up=900ms on a parent to customize timing across an entire scope.

Mental Model

Animation classes answer two questions: which keyframes should run, and how should the browser run them? Aliases answer both at once using Maple defaults, while shorthand utilities let you define custom recipes explicitly. To tune motion, use property overrides for individual elements, scoped overrides for entire sections, or keyframe variables to control spatial distance and scale. Finally, combine these with state and media selectors to activate motion only in response to user actions or responsive preferences.

Use
Syntax
When
Alias
fade-in-up

Reach for this first. It is readable and uses Maple defaults.

Shorthand
anim-fade-in-up_500_ease_both

Use when name, duration, easing, delay, or fill mode differ.

Property Override
fade-in-up animdur-900

Use when a preset is right but one property needs tuning.

Scoped Override
--animdur-fade-in-up=900ms

Use fallback chain variables on a parent or element to tune animations across a scope.

Keyframe Override
--fade-distance=96px

Use keyframe variables to customize spatial properties like distance, scale, or angle.

State
&:hover:fade-in-up

Use selectors to run motion only for hover, focus, or parent state.

Media Query
@motion-safe:fade-in-up

Use media queries to run motion based on screen sizes, dark mode, or motion preferences.

Animation Aliases

Aliases are named shortcuts that expand to complete anim-* shorthands. They keep common UI motion readable while still letting you override any part with variables or individual utilities.

For example, the fade-in alias expands to the shorthand anim-fade-in_600_ease-out_forwards. One-shot animations (such as entrances and exits) use the forwards fill mode so the element retains its final animated state. By default, entrance aliases resolve to 600ms ease-out and exit aliases to 200ms ease-in. Looping aliases run infinitely, while attention presets execute once so they can be cleanly triggered on state selectors (e.g., hover or active states).

Family
Aliases
Best For
Fade
fade-infade-out
fade-in-upfade-in-downfade-in-leftfade-in-right
fade-out-upfade-out-downfade-out-leftfade-out-right

Messages, panels, menus, and soft entrance motion.

Scale
scale-inscale-out

Popovers, badges, dialogs, and pressed feedback.

Slide
slide-in-upslide-in-downslide-in-leftslide-in-right
slide-out-upslide-out-downslide-out-leftslide-out-right

Drawers, sheets, toasts, and off-canvas elements.

Looping
spinpingpulsebouncefloatborder-beam

Loading indicators, status lights, and ambient feedback.

Attention
shakeshake-xshake-ybeatbellwiggle

Validation errors, hints, and temporary emphasis.

Use the interactive playground below to select any built-in animation alias, trigger its motion, and copy the ready-to-use HTML class name.

Fade Entrances
Fade Exits
Scale
Slide Entrances
Slide Exits
Looping
Attention
Fade Entrance
fade-in
 <!-- Opacity-only entrance for content that is already in place. -->
<div class="fade-in">Motion preview</div> 

Looping presets such as ping, pulse, and spin repeat infinitely by default, making them ideal for activity indicators, loading spinners, or ambient UI states:

ping
pulse
bounce
spin
border-beam

<!-- Ping -->
<div class="square-24 fxrow-cc rel">
  <span class="abs square-14 rad-% bgc-coral-400/25 wc-tf,o ping"></span>
  <span class="square-8 rad-% bgc-coral-500"></span>
</div>

<!-- Pulse -->
<div class="square-24 fxcol-cc g-2">
  <span class="w-9/12 h-3 rad-2 bgc-azure-500 wc-o pulse"></span>
  <span class="w-6/12 h-3 rad-2 bgc-azure-300 wc-o pulse"></span>
</div>

<!-- Bounce -->
<div class="square-24 fxrow-cc rel">
  <span class="square-10 rad-% bgc-teal-500 wc-tf bounce"></span>
  <span class="abs w-12 h-1 b-5 rad-% bgc-teal-500/20"></span>
</div>

<!-- Spin -->
<div class="square-24 fxrow-cc">
  <div class="square-10 rad-% br-2px_solid_crimson-500 brtc=transparent wc-tf spin"></div>
</div>

<!-- Border Beam -->
<div class="square-24 fxrow-cc">
  <div class="square-10 rad-2 bgc-blue-500/20 c-blue-600border-beam"></div>
</div>

Shorthand Syntax

The anim-* utility maps to CSS animation property. Separate shorthand parts with underscores. Maple classifies each token by meaning, so the class can stay compact while still producing a complete animation declaration.

Token
Property
Examples
Keyframe name
animation-name
fade-in-up
First time
animation-duration
600, 500ms, 1s
Second time
animation-delay
150, 0.2s
Timing function
animation-timing-function
linear, ease-out
Iteration
animation-iteration-count
infinite, 3
Fill, direction, state
fill / direction / play-state
both, alternate, paused

<!-- name + duration + easing + fill mode -->
<div class="anim-fade-in-up_500_ease-out_both">Panel</div>

<!-- name + duration + easing + delay + fill mode -->
<div class="anim-scale-in_300_ease-out_150_both">Popover</div>

<!-- multiple animations separated with a comma -->
<span class="anim-fade-in_300_ease-out_forwards,spin_1000_linear_infinite">
  Loading
</span>

Custom Keyframes

Maple writes animation declarations, not keyframes. For product-specific motion, define normal CSS @keyframes, reference the name with anim-*, and optionally create a custom alias on html. Put variables with fallbacks inside your keyframes when the same motion should be tunable per element.


<style>
  @keyframes slide-intro {
    from {
      opacity: 0;
      transform: translateY(var(--slide-y, 10px));
    }

    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
</style>

<!-- Use the keyframe name directly -->
<div class="anim-slide-intro_500_ease-out">Hello</div>

<!-- Or define an alias on html for cleaner markup -->
<html class="--alias-intro=anim-slide-intro_500_ease-out">
  <div class="@intro">Hello</div>
</html>

Custom Timing

Built-in aliases use a simple timing model: entrances run at 600ms ease-out, while exits run at 200ms ease-in. Override a single element with animdur-* or animdel-*. Override a scope with shared variables like --animdur, --animdel or the named ones such as --animdur-fade-in-up and --animdel-fade-in-up.

A
default
B
property
C
shared scope
D
named scope

<!-- A: Default preset timing -->
<div class="fade-in-up">Default</div>

<!-- B: Override with property utilities -->
<div class="fade-in-up animdur-900 animdel-300">Property override</div>

<!-- C: Override scope with shared variables -->
<div class="--animdur=1200ms --animdel=400ms">
  <div class="fade-in-up">Scoped timing for all animations</div>
</div>

<!-- D: Override scope with named variables -->
<div class="--animdur-fade-in-up=1500ms --animdel-fade-in-up=500ms">
  <div class="fade-in-up">Scoped timing specific to fade-in-up</div>
</div>

Keyframe Variables

Several animations expose variables inside the keyframes themselves. This lets you keep the same animation name while changing the distance, scale, opacity, or angle for a specific component. These variables change what the keyframes do, while animdur-* and friends change how the keyframes run.

Variable
Default
Use
--fade-from-opacity--fade-to-opacity
0 / 1

Starting and ending opacity values. Used by fade-in, fade-out, fade-in-*, fade-out-*

--fade-distance
48px

Distance the element travels during the fade. Used by fade-in-*, fade-out-*

--fade-scale-from--fade-scale-to
0.98

Starting and ending scale factor during the fade. Used by fade-in-*, fade-out-*

--slide-distance
100%

Distance the element travels during the slide. Used by slide-in-*, slide-out-*

--scale-from-opacity--scale-to-opacity
0 / 1

Starting and ending opacity values. Used by scale-in, scale-out

--scale-from
0.96

Starting scale factor. Used by scale-in

--scale-to
0.96

Ending scale factor. Used by scale-out

--ping-scale
2

Maximum scale factor. Used by ping

--pulse-opacity
0.5

Minimum opacity value. Used by pulse

--bounce-distance
25%

Maximum vertical displacement. Used by bounce

--float-distance
8px

Maximum vertical displacement. Used by float

--beam-core-color--beam-glow-color
currentColor

Core stroke and surrounding glow colors. Used by border-beam

--beam-tail-start
75%

Gradient start position for the fading tail. Used by border-beam

--beam-glow-start
88%

Gradient start position for the surrounding glow. Used by border-beam

--beam-core-start
92%

Gradient start position for the bright core stroke. Used by border-beam

--beam-glow-end
96%

Gradient end position for the surrounding glow. Used by border-beam

--beam-width
1px

Stroke width of the border outline. Used by border-beam

--beam-offset
0px

Displacement offset relative to the border box. Used by border-beam

--beam-radius
inherit

Border radius of the border outline. Used by border-beam

--beam-opacity
1

Overall opacity of the border outline. Used by border-beam

--shake-distance
10px

Displacement distance of the shake oscillation. Used by shake, shake-x, shake-y

--beat-scale
1.12

Maximum scale factor of the expansion. Used by beat

--bell-angle
10deg

Maximum rotation angle of the swing. Used by bell

--bell-origin
top center

Transform origin point for the rotation. Used by bell

--wiggle-angle
6deg

Maximum rotation angle of the tilt. Used by wiggle

96px
--fade-distance
0.3
--scale-from
3.5
--ping-scale
45%
--bounce-distance

<!-- Move farther before fading in -->
<div class="--fade-distance=96px fade-in-up"></div>

<!-- Start from a smaller scale -->
<div class="--scale-from=0.3 scale-in"></div>

<!-- Increase the ping radius -->
<span class="--ping-scale=3.5 ping"></span>

<!-- Bounce higher -->
<div class="--bounce-distance=45% bounce"></div>

Triggered Animations

Animation utilities work with Maple selectors, so motion can be attached to hover, focus, active, parent state, media query, and child selectors.

&:hover:shake
^.parent:hover:wiggle
^.parent:focus:beat

<!-- Runs "shake" when hovered -->
<button class="&:hover:shake">Shake</button>

<!-- Runs "wiggle" when parent is hovered over button -->
<button class="parent">
  <span class="^.parent:hover:wiggle">Wiggle</span>
</button>

<!-- Runs "beat" when focused to parent -->
<button class="parent">
  <span class="^.parent:focus:beat">Beat</span>
</button>

Individual Utilities

Individual animation utilities map directly to CSS animation properties. Use them when an alias is almost right but one part of the animation needs to change, or when you are composing your own animation from a custom keyframe name.

Utility
Property
Example
Notes
anim-*
animation
anim-fade-in

Full CSS animation shorthand.

animname-*
animation-name
animname-spin

References an existing keyframe name.

animdur-*
animation-duration
animdur-500

Numbers resolve as milliseconds.

animdel-*
animation-delay
animdel-200

Use for staggered entrance motion.

animtf-*
animation-timing-function
animtf-ease-in-out

Accepts named CSS timing functions.

animic-*
animation-iteration-count
animic-infinite

Use finite counts for attention effects.

animdir-*
animation-direction
animdir-alternate

Useful for oscillating loops.

animfm-*
animation-fill-mode
animfm-both

Use both when delayed entrances should apply their first keyframe before they start.

animps-*
animation-play-state
animps-paused

Pause or resume loops from selectors.

composed spin
override
play state

<!-- Compose a spinner from individual properties -->
<span class="animname-spin animdur-1200 animtf-linear animic-infinite">
  Loading
</span>

<!-- Override parts of a preset -->
<div class="fade-in-up animdur-900 animdel-200 animfm-both">
  Delayed entrance
</div>

<!-- Pause and resume animation play state -->
<div class="pulse animps-paused &:hover:animps-running">
  Hover to resume
</div>

Accessibility and Performance

Good UI motion is brief, purposeful, and cheap for the browser to render. Prefer opacity and transform animations, respect reduced-motion preferences, and reserve infinite motion for real progress or live status.

Practice
Guidance
Reduced motion

Gate non-essential motion with @motion-safe: or remove it with @motion-reduce:anim-none.

Restraint

Use one motion idea per interaction. Avoid chaining attention effects, long loops, and competing animated regions.

Rendering cost

Prefer transform and opacity. Avoid animating layout properties like width, height, margin, top, and left for frequent motion.

Will change

Use wc-tf,o only on elements that are about to animate. Remove broad or permanent hints from large surfaces.


<!-- Respect people who prefer less motion -->
<div class="@motion-safe:fade-in-up">
  Motion only when safe
</div>

<!-- Prefer transform and opacity, and scope will-change narrowly -->
<div class="fade-in-up wc-tf,o">
  Fast for the browser to composite
</div>

<!-- Use transition and transform for simple UI feedback -->
<button class="ts-tf_150 &:hover:scale-1.05">
  Transition instead of animation
</button>

Practical Examples

The smoothest animation work happens when motion supports a specific UI job. These examples combine aliases, timing utilities, state selectors, and keyframe variables into product patterns you can copy directly.

Use a quiet scale-in for surfaces that appear above the page.

Confirm payout
Send $120.00 to Studio
Arrives Today
Fee $0.00
<div class="cnt">
  <div data-anim-replay="scale-in" class="
    --body=oklch(0.56_0.05_260)
    --primary=oklch(0.660_0.25_260)
    wc-tf,o scale-in
  ">
    <div class="
      fxcol-sh g-5 mx-auto mxw-90 p-6 rad-5
      br brc-body-100 bgc-white @dark:bgc-black c-accent-500
      border-beam animdur=5000ms --beam-offset=1px
    ">
      <div class="fxrow-cw g-4">
        <div class="mnw-0">
          <div class="fs-5 fw=700 c-body-950">Confirm payout</div>
          <div class="mt-1 fs-3 c-body-500">Send $120.00 to Studio</div>
        </div>
        <span class="
          fxrow-cc square-9 rad-% fs-8 lh-0
          bgc-limegreen c-white
        ">⍟</span>
      </div>

      <div class="fxcol-sh g-2 px-3 py-2 rad-3 bgc-body-900/5 fs-3">
        <div class="fxrow-cw w-%">
          <span class="c-body-500">Arrives</span>
          <span class="fw=700 c-body-900">Today</span>
        </div>
        <div class="fxrow-cw w-%">
          <span class="c-body-500">Fee</span>
          <span class="fw=700 c-body-900">$0.00</span>
        </div>
      </div>

      <button class="
        p-3 rad-3 cursor=pointer 
        bgc-primary-500 c-white 
        br brc-primary-650
        &:hover:bgc-primary-450 
      ">
        Continue
      </button>
    </div>
  </div>
</div>

Animated Notifications

Use fade-in-down animation for messages that appear on top of the content.

Payment saved
Receipt sent to your inbox.
<div class="cnt">
  <div
    data-anim-replay="fade-in-down"
    class="
      w-% mx-auto mxw-90 fxrow-ss g-3 p-4 rad-4
      br brc-green-500/30 bgc-green-500/20
      wc-tf,o fade-in-down --fade-distance=96px
    "
  >
    <span class="
      fxrow-cc fxs-0 square-9 rad-% fs-3.5 fw=700
      bgc-green-500 c-white
    ">✓</span>
    <div>
      <div class="fs-4 fw=700 c-silver-950">Payment saved</div>
      <div class="fs-3 c-green-500">Receipt sent to your inbox.</div>
    </div>
  </div>
</div>

Live Sync Card

Use looped motion for ongoing work, then stagger small status rows as new sync steps complete.

LIVE SYNC
Publishing changes
Assets Uploaded
Routes Generated
Edge Cache Warming
Progress 84%
<div class="cnt">
  <div class="
    --body=oklch(0.56_0.05_260)
    --primary=oklch(0.660_0.25_260)
    fxcol-sh g-5 mx-auto mxw-90 p-6 rad-5
    br brc-body-100 bgc-white @dark:bgc-black
    c-accent-500 border-beam animdur=5000ms --beam-offset=1px
  ">
    <div class="fxrow-cw g-4">
      <div>
        <div class="fs-3 fw=600 c-body-500">LIVE SYNC</div>
        <div class="mt-1 fs-5 fw=700 c-body-950">Publishing changes</div>
      </div>
      <div class="rel fxrow-cc fxs-0 square-10 rad-% bgc-primary-500/10">
        <div class="square-6 rad-% br-2px_solid_primary-500 brtc=transparent wc-tf spin"></div>
      </div>
    </div>

    <div class="fxcol-sh g-2 p-3 rad-3 bgc-body-900/5 fs-3 of=hidden">
      <div data-anim-replay="fade-in-up" class="
        fxrow-cw w-% p-2 rad-2 bgc-white @dark:bgc-black
        wc-tf,o fade-in-up animdel-0
      ">
        <span class="c-body-500">Assets</span>
        <span class="fw=700 c-body-900">Uploaded</span>
      </div>

      <div data-anim-replay="fade-in-up animdel-100" class="
        fxrow-cw w-% p-2 rad-2 bgc-white @dark:bgc-black
        wc-tf,o fade-in-up animdel-100
      ">
        <span class="c-body-500">Routes</span>
        <span class="fw=700 c-body-900">Generated</span>
      </div>

      <div data-anim-replay="fade-in-up animdel-200" class="
        fxrow-cw w-% p-2 rad-2 bgc-white @dark:bgc-black
        wc-tf,o fade-in-up animdel-200
      ">
        <span class="c-body-500">Edge Cache</span>
        <span class="fw=700 c-body-900">Warming</span>
      </div>
    </div>

    <div class="fxcol-sh g-2">
      <div class="fxrow-cw w-% fs-3">
        <span class="c-body-500">Progress</span>
        <span class="fw=700 c-primary-500">84%</span>
      </div>
      <div class="h-2 w-% rad-10 bgc-body-900/10 of=hidden">
        <div class="h-% w-84% rad-10 bgc-primary-500 pulse &:hover:animps-paused"></div>
      </div>
    </div>
  </div>
</div>

Refer to the underlying CSS properties for granular animation control. Maple also supports newer animation properties such as animation composition and animation range utilities where the browser supports the underlying CSS.

ESC

Start typing to search across the documentation.