Motion
Motion brings interfaces to life — but only when used with purpose. Tessera UI’s motion system is minimal and functional, providing feedback for interactions without introducing distraction or delay.
Principles
Section titled “Principles”- Motion communicates, it doesn’t decorate. Every animation should help the user understand what happened — an element appeared, moved, or changed state.
- Fast by default. Most interactions use
160msor less. Users should never wait for an animation to finish. - Consistent easing. A single easing curve creates a unified feel across all transitions.
- Respect user preferences. Animations should be reduced or removed when
prefers-reduced-motionis active.
Duration Tokens
Section titled “Duration Tokens”| Token | Value | Usage |
|---|---|---|
--ts-transition-fast | 160ms | Micro-interactions: button hover, toggle switch, focus ring, color changes |
--ts-transition-normal | 240ms | State transitions: card hover elevation, component enter/exit |
--ts-transition-slow | 360ms | Layout shifts: accordion expand, panel slide, page transitions |
--ts-transition-micro | 80ms | Instant-feeling micro-feedback: checkbox tick, ripple start |
--ts-transition-x-slow | 500ms | Large-scale transitions: page-level entrances, complex orchestrations |
Loading Animation Durations
Section titled “Loading Animation Durations”| Token | Value | Usage |
|---|---|---|
--ts-loading-pulse-duration | 1.5s | Skeleton pulse cycle |
--ts-loading-shimmer-duration | 1.8s | Shimmer/sweep animation |
When to use each
Section titled “When to use each”fast(160ms) — Use for anything that responds to direct user input: hover effects, active states, focus indicators, toggle slides. The user should feel an instant response.normal(240ms) — Use for visual transitions that involve spatial change: a card lifting on hover, a modal scaling in, a tooltip appearing. Slightly longer to let the eye track the motion.slow(360ms) — Use sparingly for larger layout changes: accordion sections expanding, panels sliding in from the edge. Reserved for motions that affect page layout.
Easing Curves
Section titled “Easing Curves”Tessera UI provides four easing curves as tokens, each suited to a different type of motion:
| Token | Value | Usage |
|---|---|---|
--ts-ease-standard | cubic-bezier(0.4, 0, 0.2, 1) | Default for all transitions — natural deceleration |
--ts-ease-decelerate | cubic-bezier(0, 0, 0.2, 1) | Elements entering the screen — starts fast, slows into place |
--ts-ease-accelerate | cubic-bezier(0.4, 0, 1, 1) | Elements leaving the screen — starts slow, accelerates out |
--ts-ease-spring | cubic-bezier(0.16, 1, 0.3, 1) | Playful, bouncy motion — modal entrance, toast pop-in |
When to use each
Section titled “When to use each”- Standard — The workhorse. Use for hover effects, color transitions, and most state changes.
- Decelerate — Use when an element enters the viewport (slide-in panels, toasts appearing). The fast start draws the user’s eye.
- Accelerate — Use when an element exits. The slow start gives the user a moment to register the departure.
- Spring — Use sparingly for emphasis. Modal entrances and toast notifications benefit from the slight overshoot.
Animation Patterns
Section titled “Animation Patterns”Button press
Section titled “Button press”/* Active state */transform: scale(0.97);transition: transform var(--ts-transition-fast);A subtle scale-down on press provides tactile feedback. The 3% reduction is enough to feel without being distracting.
Modal entrance
Section titled “Modal entrance”/* Overlay */@keyframes ts-fade-in { from { opacity: 0; } to { opacity: 1; }}/* Duration: 200ms, ease-out */
/* Dialog */@keyframes ts-scale-in { from { opacity: 0; transform: scale(0.95) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); }}/* Duration: 240ms, cubic-bezier(0.16, 1, 0.3, 1) */Modals use a two-part entrance: the overlay fades in first, then the dialog scales up from slightly below center. The dialog uses a more pronounced ease-out (spring-like) for a polished feel.
Tooltip appearance
Section titled “Tooltip appearance”opacity: 0 → 1;transform: scale(0.95) → scale(1);transition: opacity, transform var(--ts-transition-fast);Tooltips fade and scale up from 95% — a quick, minimal entrance that doesn’t draw focus.
Toggle switch
Section titled “Toggle switch”transition: left var(--ts-transition-fast);/* Track background also transitions */transition: background-color var(--ts-transition-fast);The thumb slides horizontally while the track color transitions simultaneously.
Card hover elevation
Section titled “Card hover elevation”transition: box-shadow var(--ts-transition-normal), transform var(--ts-transition-normal);/* On hover: shadow increases, card lifts 2px */Interactive cards use normal duration because the shadow spread change is spatially larger than a color change.
Transition Properties
Section titled “Transition Properties”Always list specific properties in transitions — never use transition: all:
/* Good */transition: background-color var(--ts-transition-fast), border-color var(--ts-transition-fast), color var(--ts-transition-fast);
/* Bad — triggers layout recalculation for every property */transition: all var(--ts-transition-fast);Choreography / Stagger System
Section titled “Choreography / Stagger System”When multiple elements enter the screen at once (list items, grid cards, dashboard widgets), stagger their entrance to create a choreographed sequence rather than a simultaneous flash.
Tessera UI provides a stagger interval token:
| Token | Value | Usage |
|---|---|---|
--ts-stagger-interval | 50ms | Delay between each item’s entrance animation |
Implementation example:
.stagger-list > * { opacity: 0; transform: translateY(8px); animation: ts-stagger-enter var(--ts-transition-normal) var(--ts-ease-decelerate) forwards;}
/* Each child delays by its index * interval */.stagger-list > *:nth-child(1) { animation-delay: calc(1 * var(--ts-stagger-interval)); }.stagger-list > *:nth-child(2) { animation-delay: calc(2 * var(--ts-stagger-interval)); }.stagger-list > *:nth-child(3) { animation-delay: calc(3 * var(--ts-stagger-interval)); }.stagger-list > *:nth-child(4) { animation-delay: calc(4 * var(--ts-stagger-interval)); }/* ... continue as needed, or use a CSS custom property with JavaScript */
@keyframes ts-stagger-enter { to { opacity: 1; transform: translateY(0); }}Guidelines for stagger:
- Cap stagger at 8-10 items — beyond that, the total delay becomes noticeable
- Total stagger sequence should not exceed
500ms(--ts-transition-x-slow) - Use
prefers-reduced-motionto disable stagger entirely, showing all items immediately
Reduced Motion
Section titled “Reduced Motion”Users who set prefers-reduced-motion: reduce in their OS or browser preferences should see minimal or no animation. Components should respect this:
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }}This is recommended at the application level. Essential state changes (like toggle position) should still be instant but skip the animation.
Guidelines
Section titled “Guidelines”- Never block interaction with animation. Users should be able to click, type, or navigate without waiting for an animation to complete.
- Don’t animate layout properties (
width,height,top,left). Usetransformandopacityfor performant animations that don’t trigger layout recalculation. - Match duration to distance. Small movements (hover color) =
fast. Medium movements (card lift) =normal. Large movements (panel slide) =slow. - Entrance animations only. Components animate when appearing but disappear instantly (or with a very fast fade). Users don’t want to wait for something to leave.