motion · live
Animation
Every keyframe in the system reads from --duration-* and --ease, so swapping themes changes the feel — Terminal snaps, Glass floats, Press drifts — without a single code change.
Try it now: open the theme dropdown in the header (or the floating ThemePicker in the bottom-right) and watch every preview below retime in place.
Live grid
Every animation in the library, rendered as a card. One-shot animations play on hover or click; looping animations run continuously so you can read the rhythm. The utility class shown on each card is the easiest way to trigger the animation in your own markup.
Hover a card's preview area, click it, or focus it with the keyboard to replay one-shot animations. Loops never stop.
One-shot animations
cia-fade-in
normalOpacity 0 to 1. The default reveal for content arriving after mount.
<div class="cia-anim-fade-in">…</div>
cia-fade-out
normalOpacity 1 to 0. Pair with unmount to dismiss toasts and overlays.
<div class="cia-anim-fade-out">…</div>
cia-slide-up
normalRises 8px while fading in. Reach for it on dropdowns and popovers.
<div class="cia-anim-slide-up">…</div>
cia-slide-down
normalDrops 8px while fading in. Header banners and inline alerts.
<div class="cia-anim-slide-down">…</div>
cia-slide-left
normalSlides in from the right while fading in.
<div class="cia-anim-slide-left">…</div>
cia-slide-right
normalSlides in from the left while fading in.
<div class="cia-anim-slide-right">…</div>
cia-scale-in
normalGrows from 0.96 to 1 while fading in. Modal bodies, tooltip content.
<div class="cia-anim-scale-in">…</div>
cia-pop
normalBrief beat 1 to 1.06 to 1. Notification badges, success ticks.
<div class="cia-anim-pop">…</div>
cia-wiggle
normalRotation jitter. Invalid-input feedback, attention nudges.
<div class="cia-anim-wiggle">…</div>
Looping animations
cia-pulse
slowOpacity loop 1 to 0.55 to 1. Live indicators, subtle awaits.
<div class="cia-anim-pulse">…</div>
cia-spin
slowContinuous 360 degree rotation, linear timing. Spinners, refresh.
<div class="cia-anim-spin">…</div>
cia-shimmer
slowSweeps a gradient across the element. Skeleton loaders.
<div class="cia-anim-shimmer">…</div>
Motion tokens
The whole animation system reads from four CSS custom properties. Swap the theme and every transition, hover, and keyframe retimes without any code change.
| Token | Role | Sketchbook default |
|---|---|---|
--duration-fast | Hover, focus, button press — interactions that must feel immediate. | 180ms |
--duration-normal | Enter / exit transitions, tooltip + popover reveals. | 240ms |
--duration-slow | Looping ambients — pulse, spin, shimmer, wiggle. | 380ms |
--ease | Default easing curve for every transition and non-linear keyframe. | cubic-bezier(0.33, 0.66, 0.33, 1) |
Reach for var(--duration-*) in your own SCSS or inline styles — never hard-code 180ms. A button that reads the token is a button that ships with every theme.
.my-btn { transition: background var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease); } .my-btn:hover { background: var(--brand-primary); color: var(--text-inverse); }
Utility classes
Every keyframe is exposed as a .cia-anim-<name> utility, with -fast and -slow speed variants. Three loopers — spin, pulse, shimmer — ship with an infinite iteration count built in, because that is how they are used 99% of the time.
<div class="cia-anim-fade-in">…</div> <div class="cia-anim-slide-up-fast">…</div> <span class="cia-anim-pulse">live</span> <div class="skeleton cia-anim-shimmer"></div>
Hover utilities
Four hover primitives cover the interactions the system needs most. Each is a token-driven micro-transition — no motion values are hard-coded, so they adopt the active theme's timing curve.
| Class | Effect on hover | Reads from |
|---|---|---|
.cia-hover-lift | translateY(-2px) + medium shadow. | --duration-fast, --shadow-md |
.cia-hover-glow | Medium glow via box-shadow. | --duration-fast, --glow-md |
.cia-hover-press | translateY(1px) scale(0.99) — button-press feel. | --duration-fast |
.cia-hover-fade | Opacity dips to 0.7. | --duration-fast |
<a class="cia-card cia-hover-lift">…</a> <button class="cia-btn cia-hover-press">…</button> <img class="cia-hover-fade" src="/thumb.jpg" alt="">
The animate() mixin
For authored components, _animations.scss exposes an animate() mixin that wires duration, easing, iteration, and reduced-motion in one call.
// signature @mixin animate($name, $speed: normal, $delay: 0s, $iteration: 1, $fill: both, $timing: var(--ease)); // usage .toast { @include animate(slide-up); } .spinner { @include animate(spin, $speed: slow, $iteration: infinite, $timing: linear); }
The mixin validates animation names at compile time — pass an unknown name and the build fails with a list of the legal ones.
Reduced motion
Every animation on this page — both the live cards above and the utility classes you copy into your own markup — respects prefers-reduced-motion: reduce. Users with that setting enabled see the final state of each animation immediately: no fade, no slide, no pulse, no spin. Hover-driven transitions are suppressed too. Nothing is hidden, nothing is moved off-screen — the content is simply rendered without motion.
The guarantee comes from three layers in _animations.scss: the animate() mixin collapses its own animation to 0.01ms, the transition() mixin drops transitions entirely, and a global * rule defuses any animation or transition declared anywhere in the document.
// scss/_animations.scss — global safety net @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }
Writing your own
- Use the motion tokens. Type
var(--duration-fast)/var(--ease), never raw180msor a bespokecubic-bezier. Your animation re-skins when a theme swaps the tokens. - Prefer
transformandopacity. Both are GPU-accelerated and do not trigger layout. Animatingwidth,height, ortopwill jank on low-end hardware. - Wrap new keyframes in a reduced-motion check, or reach for the
animate()/transition()mixins that already do it. A user who opts out of motion should opt out of your motion too. - Keep the vocabulary small. If the library's twelve keyframes cover the intent, use them — a consistent motion language reads as a single voice, not a grab-bag of easings.