Skip to Content

Motion

Motion in the Mukoko design system is purposeful and restrained. Animations communicate state changes and guide attention — they never exist for decoration.

Principles

  1. Functional, not decorative — every animation must communicate something (a state change, a transition, a response to user input)
  2. Fast — most transitions complete in 150-200ms; users should never wait for an animation
  3. Consistent — the same type of change uses the same animation everywhere
  4. Respectful — honor prefers-reduced-motion for users who need it

Timing

DurationCSS classUsage
100msduration-100Micro-interactions (hover, focus)
150msduration-150State changes (toggle, checkbox)
200msduration-200Standard transitions (color, opacity)
300msduration-300Layout shifts (accordion, collapsible)
500msduration-500Page-level transitions (sheet, drawer)

Easing

EasingCSS classUsage
ease-outease-outElements entering (fade in, slide in)
ease-inease-inElements exiting (fade out, slide out)
ease-in-outease-in-outContinuous motion (loading spinners)

Standard transition pattern:

<div className="transition-colors duration-150 ease-out hover:bg-muted"> Hover me </div>

Enter/exit patterns

Fade

The simplest transition. Used for tooltips, popovers, and content that appears in place:

{/* Tailwind transition */} <div className="transition-opacity duration-200 ease-out data-[state=open]:opacity-100 data-[state=closed]:opacity-0"> Content </div>

Slide

Used for sheets, drawers, and sidebars that enter from an edge:

{/* Sheet sliding from bottom */} <SheetContent side="bottom" className="transition-transform duration-300 ease-out"> Content </SheetContent>

Scale

Used for dialogs and modals. Scale from 95% to 100% with a simultaneous fade:

<div className="transition-all duration-200 ease-out data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"> Modal content </div>

Collapse

Used for accordions and collapsible sections. Animate height from 0 to auto:

<AccordionContent> {/* Radix handles the height animation */} Content that expands and collapses </AccordionContent>

Radix UI primitives handle these animations internally with CSS keyframes.

Common animation patterns

Hover lift

Cards and interactive elements that lift slightly on hover:

<Card className="transition-all duration-150 hover:shadow-md hover:-translate-y-0.5"> Content </Card>

Loading spinner

The Spinner component uses a continuous rotation:

<Spinner className="size-5" />

Skeleton pulse

Loading placeholders pulse with a subtle opacity animation:

<Skeleton className="h-4 w-48" />

Arrow slide

Navigation arrows that slide on hover to indicate direction:

<ArrowRight className="size-4 transition-transform duration-150 group-hover:translate-x-1" />

Reduced motion

Respect the prefers-reduced-motion media query. Users enable this for medical reasons (vestibular disorders, seizure conditions) or personal preference.

@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; } }

In Tailwind, use the motion-safe and motion-reduce variants:

{/* Only animate when motion is OK */} <div className="motion-safe:transition-transform motion-safe:duration-200 motion-safe:hover:-translate-y-1"> Card </div> {/* Alternative for reduced motion */} <div className="motion-reduce:transition-none"> Content </div>

What to keep with reduced motion

Even with reduced motion enabled:

  • Opacity changes are acceptable (instant, no physical motion)
  • Color changes are acceptable (hover states, focus states)
  • Layout shifts should still happen, just without animation

What to remove:

  • Translate/scale/rotate animations
  • Sliding transitions
  • Parallax effects
  • Auto-playing carousels
Last updated on