Motion
Bumi's motion language is purposeful and restrained. Animations serve function --- they communicate state changes, guide attention, and provide feedback. They never exist for decoration.
Live Animation Demos (hover & watch)
Hover me
Card Lift
Card Lift
200ms ease-out
Skeleton Shimmer
Online
Pulse Dot (2s)
Button Hover
Duration Scale
| Token | Duration | Usage |
|---|---|---|
duration-micro | 100ms | Hover color changes, opacity shifts |
duration-fast | 150ms | Button press feedback, icon transitions |
duration-normal | 200ms | Panel expand/collapse, fade transitions |
duration-slow | 300ms | Modal enter/exit, sheet slide, dropdown open |
duration-enter | 500ms | Page transitions, staggered content load |
Easing Curves
| Context | Easing | CSS Value |
|---|---|---|
| Enter / appear | ease-out | cubic-bezier(0, 0, 0.2, 1) |
| Exit / dismiss | ease-in | cubic-bezier(0.4, 0, 1, 1) |
| State change | ease-in-out | cubic-bezier(0.4, 0, 0.2, 1) |
| Spring (modal/sheet) | custom | cubic-bezier(0.16, 1, 0.3, 1) |
Common Animations
Hover: Card Lift
A subtle upward shift on card hover. Paired with shadow transition from Level 1 to Level 2.
css
.card {
transition: transform 100ms ease-out, box-shadow 100ms ease-out;
}
.card:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-2);
}Hover: Button
Background color change only. No transform.
css
.btn-primary {
transition: background-color 150ms ease-in-out;
}
.btn-primary:hover {
background-color: var(--color-brand-hover);
}Modal Enter
Overlay fades in while content slides up from below.
css
/* Overlay */
.modal-overlay {
animation: fade-in 200ms ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Content */
.modal-content {
animation: slide-up 300ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Sheet (Slide from Right)
css
.sheet {
animation: slide-right 300ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slide-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}Page Load: Staggered Fade-Up
Each section on the page fades in with a staggered delay.
css
.page-section {
animation: fade-up 500ms ease-out both;
}
.page-section:nth-child(1) { animation-delay: 0ms; }
.page-section:nth-child(2) { animation-delay: 50ms; }
.page-section:nth-child(3) { animation-delay: 100ms; }
.page-section:nth-child(4) { animation-delay: 150ms; }
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Tab Switch
No animation. Content swaps instantly.
css
/* Tab content changes are immediate --- no transition */
.tab-content {
/* No animation property */
}Skeleton Shimmer
css
.skeleton {
background: linear-gradient(
90deg,
var(--bg-inset) 0%,
var(--bg-hover) 50%,
var(--bg-inset) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite linear;
}
@keyframes shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}Live Indicator Pulse
css
.live-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--color-success);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}React Native (Mobile)
Use react-native-reanimated for mobile animations. Spring configs for common transitions:
tsx
import { withSpring, withTiming } from 'react-native-reanimated';
// Modal slide-up
const modalSpring = {
damping: 20,
stiffness: 300,
mass: 0.8,
};
// Sheet slide
const sheetSpring = {
damping: 25,
stiffness: 250,
mass: 1,
};
// Button press
const pressConfig = {
duration: 150,
};CSS Custom Properties
css
:root {
--duration-micro: 100ms;
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--duration-enter: 500ms;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
}DO and DON'T
DO
- Use ease-out for elements entering the screen
- Use ease-in for elements leaving the screen
- Keep hover animations at 100ms --- they should feel instant
- Use staggered delays for lists of content loading in
DON'T
- Never use
bounceorelasticeasing --- this is a business application, not a game - Never animate layout properties (
width,height,top,left) --- usetransformandopacityonly - Don't add transition to tab content changes --- instant swap feels more responsive
- Don't exceed 500ms for any single animation --- users will perceive it as sluggish