rise
640ms · 70ms staggerEntrance. Opacity and a 12px lift, siblings staggered.
Cube is a JavaScript animation library with no dials. rise, leave, morph and reveal each ship with the duration, curve and stagger chosen for that job. Web Animations API underneath, zero dependencies.
Entrance. Opacity and a 12px lift, siblings staggered.
Exit. Opacity and a 12px drop, held until you remove the node. Half the entrance.
State change. Text keeps shared leading letters and blurs the rest in and out. Icons crossfade as whole faces. The parent’s width follows.
Below the fold. Hidden until it enters the viewport, then rises once. Scroll the box.
import { rise, leave, morph, reveal } from "cube-motion";
rise(".hero", { targets: "children" }); // staggered entrance
leave(toast); // staggered exit, holds the end state
morph(oldIcon, newIcon); // one state into the next
reveal(".card"); // rise when scrolled into viewimport { Rise, Morph, Reveal } from "cube-motion/react";
<Rise as="section" targets="children" className="hero"> // stagger children
<h1>Four motions.</h1>
<button onClick={save}>
<Morph active={saved} off="Save" on="Saved" />
</button>
</Rise>
<Rise show={open} className="toast">Saved</Rise> // rises in, leaves before unmount
<Reveal as="ul" targets="children" className="cards">{cards}</Reveal><script setup>
import { Rise, Morph, Reveal } from "cube-motion/vue";
</script>
<template>
<Rise as="section" targets="children" class="hero"> <!-- stagger children -->
<h1>Four motions.</h1>
<button @click="save"><Morph :active="saved" off="Save" on="Saved" /></button>
</Rise>
<Rise :show="open" class="toast">Saved</Rise> <!-- rises in, leaves before unmount -->
<Reveal as="ul" targets="children" class="cards">
<li v-for="card in cards" :key="card.id">{{ card.title }}</li>
</Reveal>
</template>import { Rise, Morph, Reveal } from "cube-motion/solid";
<Rise as="section" targets="children" class="hero"> // stagger children
<h1>Four motions.</h1>
<button onClick={save}>
<Morph active={saved()} off="Save" on="Saved" />
</button>
</Rise>
<Rise show={open()} class="toast">Saved</Rise> // rises in, leaves before unmount
<Reveal as="ul" targets="children" class="cards">{cards}</Reveal><script>
import { rise, leave, morph, reveal } from "cube-motion/svelte";
</script>
<button onclick={save}>
<span class="faces" use:morph={saved}>
<span class:inactive={saved} aria-hidden={saved} inert={saved}>Save</span>
<span class:inactive={!saved} aria-hidden={!saved} inert={!saved}>Saved</span>
</span>
</button>
{#if open}<div class="toast" in:rise out:leave>Saved</div>{/if}
<ul use:reveal={{ targets: "children" }} class="cards">…</ul>Duration, curve and distance are fixed for each job. Stagger and delay control when the motion starts.
Cube covers entrances, exits, state changes and scroll reveals. Use another tool when you need timelines, gestures or layout animation.
Web Animations API and IntersectionObserver. Nothing to polyfill, no frame loop, nothing fighting the browser.
They give you every dial. Cube gives you four decisions already made. Reach for them when you need a timeline, gestures or layout animation.
The core has zero runtime dependencies and uses the browser’s Web Animations API. React, Vue, Solid and Svelte are optional peer dependencies for their adapters.
Press feedback is three lines of CSS: a transition on scale and :active { scale: 0.97 }. CSS also answers keyboard activation, which a pointer listener never did, so the function was retired and its place went to leave.
Cube uses a fixed cubic-bezier curve, not a spring solver. There are no spring options.
Opacity changes stay; translation, scale and blur go. rise and leave become fades, morph a crossfade. reveal applies the reduced-motion preference when an element enters view.