Movimiento
El movimiento explica un cambio; nunca decora. La página está completa sin él.
Antes de animar
- 01
¿Qué tan seguido pasa?
Lo repetido (escribir, alternar, hover) se siente casi instantáneo o no se anima. El movimiento expresivo es para momentos raros.
- 02
¿Para qué sirve?
Explicar el espacio, confirmar una acción, mostrar un estado o suavizar un cambio. Si no hace ninguna, no se anima.
- 03
¿Qué física usa?
--ease-out para entrar y salir, --ease-in-out para mover lo que ya está en pantalla, lineal solo para progreso y resortes para gestos.
- 04
¿Cómo se ve con movimiento reducido?
Se diseña al mismo tiempo: conserva opacidad, color y cambios de estado; quita recorrido, escala, parallax y rebote.
Tokens
| Token | Valor | Uso |
|---|---|---|
--ease-out | cubic-bezier(.22, 1, .36, 1) | Entradas, salidas, press y hover: responde de inmediato y se asienta. |
--ease-in-out | cubic-bezier(.65, 0, .35, 1) | Algo que ya está en pantalla y se mueve a otro lugar. |
--ease-linear | linear | Solo progreso. |
--ease-expressive | cubic-bezier(.32, .72, 0, 1) | Demos, video y cambios espaciales grandes. |
--duration-fast | 150ms | Press y hover (100–160ms). |
--duration-pop | 180ms | Tooltips y popovers (125–200ms). |
--duration-menu | 220ms | Dropdowns y selects (150–250ms). |
--duration-panel | 320ms | Modales, drawers y sheets (200–500ms). |
--duration-slow | 700ms | Solo demos de marketing y video. |
--es-lift | 8px | Recorrido de una entrada en interfaz. Video usa 24px. |
--es-press | .96 | Escala al presionar cualquier control. |
Tiempos
Menos de 300ms por defecto en la interfaz. Lo más largo es para demos, gestos deliberados y cambios espaciales grandes.
| Interacción | Token | Rango | Sensación |
|---|---|---|---|
| Press y hover | --duration-fast | 100–160ms | Inmediato y físico |
| Tooltip, popover | --duration-pop | 125–200ms | Rápido, nace de su origen |
| Dropdown, select | --duration-menu | 150–250ms | Responde sin esperar |
| Modal, drawer, sheet | --duration-panel | 200–500ms | Lo justo para explicar el espacio |
| Demo de marketing, video | --duration-slow | Flexible | La claridad pesa más que la velocidad |
Press
Confirma la acción antes de que termine: escala a 0.96 en 150ms. Es lo que hace Button.
<button className="transition-[scale,background-color] duration-(--duration-fast) ease-out active:scale-(--es-press) motion-reduce:active:scale-100">
Continuar
</button>Entrada
Una superficie a la vez sube 8px y aparece, y termina antes de volverse el foco. Las listas se escalonan 30–50ms desde lo que tocaste.
.reveal {
animation: reveal 700ms var(--ease-out) both;
animation-delay: calc(var(--d, 0) * 100ms); /* escalona 100ms por pieza */
}
@keyframes reveal {
from { opacity: 0; translate: 0 var(--es-lift); }
}
/* Movimiento reducido: el contenido simplemente está ahí. */
@media (prefers-reduced-motion: reduce) {
.reveal { animation: none; }
}Íconos con significado
// El ícono actúa su significado: la flecha gira hacia su destino.
// Solo con puntero y nunca con movimiento reducido.
<ArrowRight className="transition-[rotate] duration-(--duration-fast) ease-out group-hover:-rotate-45 motion-reduce:group-hover:rotate-0" />Continuidad y cambio de contenido
- Cuando una superficie cambia de tamaño o lugar, se mantiene visible: primero se mueve su forma y después entra su etiqueta.
- En un cambio de contenido, lo viejo sale más rápido que lo nuevo (cerca del 60% del tiempo) y viaja pocos píxeles.
- Un menú, popover o sheet nace del control que lo abrió (transform-origin) y se va por donde vino.
Gestos
import { motion, useReducedMotion } from "motion/react"
// Gestos con resortes: críticamente amortiguados (sin rebote, ~0.4s).
// Animan desde el valor actual, así el gesto se puede tomar y revertir.
const reduce = useReducedMotion()
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 0 }}
transition={reduce ? { duration: 0 } : { type: "spring", bounce: 0, duration: 0.4 }}
/>Movimiento reducido
Es un estado diseñado, no una ausencia: conserva opacidad, color y cambios instantáneos; quita recorrido, escala, parallax y rebote. Ningún cambio de estado vive solo en una animación.
const hidden = { opacity: 0, translate: reduce ? "0 0" : "0 var(--es-lift)" }
const visible = { opacity: 1, translate: "0 0" }Nunca
- Marquesinas que se desplazan solas.
- Efecto máquina de escribir.
- Puntos de estado que laten.
- Parallax.
- Revelar cada sección al hacer scroll.
- Rebote en cosas que nadie lanzó.
- Animar algo que no sea transform u opacity.