MiHCM

Motion & Animation

Motion is purposeful. Durations are short enough to keep the UI feeling responsive — never decorative for its own sake. Easings echo physical motion: things that arrive decelerate, things that leave accelerate.

All motion honours prefers-reduced-motion at the user-agent level — no per-component opt-in needed. If you author a custom animation, gate it the same way.

Tokens at a glance

Reach for duration-short + ease-standard by default. Use longer durations for entrances and layout shifts where the user expects the motion to last a moment; use instant only when a transition would actually feel slower than no transition at all.

Duration

TokenValueUse
instant0msToggles, immediate state changes.
micro150msHover/focus state changes.
short200msDefault for most interactions.
medium300msLayout shifts, drawers.
long500msEntrances, modals appearing.

Easing

TokenValueUse
standardcubic-bezier(0.4, 0, 0.2, 1)Default — symmetric, neutral.
entercubic-bezier(0, 0, 0.2, 1)Things appearing — decelerating.
exitcubic-bezier(0.4, 0, 1, 1)Things leaving — accelerating.

Animations

The design system ships two layers of animation utilities. Custom keyframes are tailored to our components (dialog, dropdown, tooltip, sheet). Composable enter/exit animations from tw-animate-css let you combine fade, zoom, slide, and spin modifiers on any element with Radix data-state attributes.

In the live examples below, exit animations briefly hold the resting state before playing out. That mirrors real component behavior: the element exists while the close state is applied, then it is removed after the animation completes.

Custom keyframes

dialog-in

Fade + scale up from 95 %

dialog-out

Fade + scale down to 95 %

dropdown-in

Fade + scale + slide from top

fade-in

Simple opacity entrance

slide-in-right

Slide from right edge

slide-in-left

Slide from left edge

slide-in-top

Slide from top edge

slide-in-bottom

Slide from bottom edge

Composable enter

Fade

animate-in fade-in-0

Fade + Zoom

animate-in fade-in-0 zoom-in-95

Slide from top

animate-in fade-in-0 slide-in-from-top-2

Slide from bottom

animate-in fade-in-0 slide-in-from-bottom-2

Slide from left

animate-in fade-in-0 slide-in-from-left-2

Slide from right

animate-in fade-in-0 slide-in-from-right-2

Dropdown combo

animate-in fade-in-0 zoom-in-95 slide-in-from-top-2

Composable exit

Fade out

animate-out fade-out-0

Fade + Zoom out

animate-out fade-out-0 zoom-out-95

Slide to top

animate-out fade-out-0 slide-out-to-top-2

Slide to bottom

animate-out fade-out-0 slide-out-to-bottom-2

Custom keyframes

These are defined in @mihcm/tokens and use our duration/easing tokens. Each is a single Tailwind class.

ClassDurationEasingUsed by
animate-dialog-in260msstandard smoothDialog, AlertDialog
animate-dialog-out220msexitDialog closing
animate-dropdown-in220msenter smoothDropdownMenu, ContextMenu, Select, Combobox, Popover
animate-fade-in240msstandard smoothTooltip
animate-slide-in-right420msstandard smoothSheet (side=right)
animate-slide-in-left420msstandard smoothSheet (side=left)
animate-slide-in-top420msstandard smoothSheet (side=top)
animate-slide-in-bottom420msstandard smoothSheet (side=bottom)

Composable enter/exit system

Powered by tw-animate-css. Start with animate-in or animate-out, then layer modifiers to control fade, scale, slide, and rotation. Works with Radix data-[state=open/closed] attributes for automatic enter/exit.

Base classes

ClassDescription
animate-inStarts the enter animation (plays the enter keyframe).
animate-outStarts the exit animation (plays the exit keyframe).

Modifiers

EnterExitEffect
fade-in-0fade-out-0Opacity 0 to 1 / 1 to 0
zoom-in-95zoom-out-95Scale from 95% / to 95%
slide-in-from-top-2slide-out-to-top-2Translate Y from -8px / to -8px
slide-in-from-bottom-2slide-out-to-bottom-2Translate Y from 8px / to 8px
slide-in-from-left-2slide-out-to-left-2Translate X from -8px / to -8px
slide-in-from-right-2slide-out-to-right-2Translate X from 8px / to 8px

Radix data-state pattern

The most common usage: pair enter/exit with Radix data attributes so the component animates on open and close automatically.

// HoverCard, NavigationMenu, etc.
className={cn(
  'data-[state=open]:animate-in data-[state=closed]:animate-out',
  'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
  'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
  'data-[side=bottom]:slide-in-from-top-2',
  'data-[side=top]:slide-in-from-bottom-2',
)}

Built-in height animations

ClassDescriptionUsed by
animate-accordion-downHeight 0 to auto (expand)Accordion
animate-accordion-upHeight auto to 0 (collapse)Accordion
animate-collapsible-downHeight 0 to auto (expand)Collapsible
animate-collapsible-upHeight auto to 0 (collapse)Collapsible
animate-caret-blinkPulsing cursor blinkInputOTP

Reduced motion

tw-animate-css automatically respects prefers-reduced-motion: reduce by setting animation duration to near-zero. Our custom keyframes use CSS variables that can be overridden the same way. No additional work needed in consuming code.

Guidelines

  • Entrances decelerate (ease-enter) — content arrives and settles.
  • Exits accelerate (ease-exit) — content gathers speed as it leaves.
  • Keep it under 300ms — anything longer feels sluggish. Use duration-medium (300ms) only for sheet/drawer slides.
  • Fade + scale = polish — combine fade-in-0 zoom-in-95 for dropdown/popover entrances. Pure fades feel flat.
  • Match direction to origin — a dropdown opening below should slide-in-from-top. A sheet opening right should slide-in-from-right.
  • No animation on hover states — use transition-colors duration-150 instead. Hover feedback must be instant.