Saltar al contenido
ssntl/ui

Movimiento

El movimiento explica un cambio; nunca decora. La página está completa sin él.

Antes de animar

  1. 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.

  2. 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.

  3. 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.

  4. 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

TokenValorUso
--ease-outcubic-bezier(.22, 1, .36, 1)Entradas, salidas, press y hover: responde de inmediato y se asienta.
--ease-in-outcubic-bezier(.65, 0, .35, 1)Algo que ya está en pantalla y se mueve a otro lugar.
--ease-linearlinearSolo progreso.
--ease-expressivecubic-bezier(.32, .72, 0, 1)Demos, video y cambios espaciales grandes.
--duration-fast150msPress y hover (100–160ms).
--duration-pop180msTooltips y popovers (125–200ms).
--duration-menu220msDropdowns y selects (150–250ms).
--duration-panel320msModales, drawers y sheets (200–500ms).
--duration-slow700msSolo demos de marketing y video.
--es-lift8pxRecorrido de una entrada en interfaz. Video usa 24px.
--es-press.96Escala 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ónTokenRangoSensación
Press y hover--duration-fast100–160msInmediato y físico
Tooltip, popover--duration-pop125–200msRápido, nace de su origen
Dropdown, select--duration-menu150–250msResponde sin esperar
Modal, drawer, sheet--duration-panel200–500msLo justo para explicar el espacio
Demo de marketing, video--duration-slowFlexibleLa 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.

tsx
<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.

css
.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

tsx
// 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

tsx
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.

ts
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.