Skip to main content
← All Foundations

Canon

Motion

Motion should be purposeful, not decorative. Animation reveals state changes and guides attention. When in doubt, don't animate.

Published January 8, 2026

Motion Philosophy

Every animation must answer: what does this communicate that stillness cannot? Motion exists to reduce cognitive load, not increase visual complexity.

Purposeful

Motion communicates state change. No decorative animation.

Subtle

Users should feel the effect, not notice the animation.

Consistent

One easing curve for coherent motion language.

Reducible

Always respect prefers-reduced-motion.

Duration Tokens

Five duration levels from instant feedback to deliberate reveals.

TokenValueUse Case
--duration-instant0msImmediate state changes
--duration-micro100msHover states, button feedback
--duration-fast200msTooltips, dropdowns
--duration-normal300msModal transitions, page elements
--duration-slow500msComplex reveals, hero animations

Easing

Canon uses a single easing curve for consistency:

--ease-standard: cubic-bezier(0.4, 0.0, 0.2, 1);

This is Material Design’s standard easing—quick acceleration, gradual deceleration. It feels natural because it mimics physical motion.

Accessibility

Always respect user preferences:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Usage Examples

Button Hover

.button {
  transition: all var(--duration-micro) var(--ease-standard);
}

.button:hover {
  transform: translateY(-1px);
}

Modal Entrance

.modal {
  animation: fadeIn var(--duration-normal) var(--ease-standard);
}

@keyframes fadeIn {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}

Dropdown

.dropdown {
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}

.dropdown[data-state="closed"] {
  opacity: 0;
  transform: translateY(-4px);
}