Motion

Cuándo no animar

7 min

El command menu más descargado de la web no tiene animación de entrada. No es un olvido.

cmdk se usa en Linear, Vercel, Raycast. Se abre cientos de veces al día. La decisión de shiipearlo sin animación al entrar ni al salir es la mejor decisión de diseño del componente.

Si animas algo que ocurre 200 veces al día, no creas deleite. Creas fricción que se acumula.

El test de frecuencia

Antes de cualquier animación, una pregunta: ¿cuántas veces al día lo triggerea un usuario típico?

El botón de feedback que se morfosea y parece mágico la primera vez resulta irritante para el jueves. La animación no ha cambiado — la novedad ha expirado, y ahora pagas el coste de duración en cada interacción.

Acciones de teclado — la regla más difícil de seguir

Nunca animes acciones iniciadas por teclado. Sin excepciones.

Cuando pulsas una tecla, esperas respuesta instantánea. El disconnect entre el input del periférico y la pantalla significa que la animación no añade valor físico — solo crea latencia percibida.

Teclas de flecha en una lista. Cmd+Tab. Cualquier atajo. Si empieza con una tecla, la respuesta debe sentirse inmediata.

El App Switcher de macOS — sin animación. Cmd+Tab varias veces por segundo, a lo largo de miles de sesiones diarias. Una transición de 200ms se sentiría rota desde el primer día.

El patrón:

/* Con animación — se siente lento en teclado */
.list-item {
  transition: background 150ms ease-out;
}

/* Sin — correcto para navegación por teclado */
.list-item {
  background: transparent;
  /* Sin transition. La velocidad ES el feedback. */
}
Teclado: feedback instantáneo. La velocidad es la animación.
let isKeyboardNav = false;

document.addEventListener('keydown', () => {
  isKeyboardNav = true;
});

document.addEventListener('mousedown', () => {
  isKeyboardNav = false;
});

function highlightItem(el: HTMLElement) {
  if (isKeyboardNav) {
    el.setAttribute('data-active', 'true');
  } else {
    el.classList.add('is-transitioning');
    el.setAttribute('data-active', 'true');
  }
}
Sin transiciones cuando isKeyboardNav — flechas, Cmd+Tab, cualquier atajo.

El límite de los 300ms

Toda animación de UI tiene un límite estricto: 300ms.

Pasados los 300ms, el usuario deja de percibir el motion como feedback y empieza a percibirlo como retraso.

La tabla de duraciones:

Elemento Duración
Button press, scale 100–150ms
Tooltips, dropdowns 150–250ms
Modals, drawers 200–300ms
Transiciones de página 250–350ms
Guía de duración — las animaciones de UI deben quedarse por debajo de 300ms.

Los elementos más grandes animan más lento. Las salidas son un 20% más rápidas que las entradas. La salida no necesita notarse — solo no ser abrupta.

:root {
  --duration-fast:       150ms;
  --duration-base:       250ms;
  --duration-slow:       350ms;
  --duration-exit-fast:  120ms;
  --duration-exit-base:  200ms;
  --duration-exit-slow:  280ms;

  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.87, 0, 0.13, 1);
}

.dropdown[data-state="open"] {
  animation: slideDown var(--duration-base) var(--ease-out) forwards;
}

.dropdown[data-state="closed"] {
  animation: slideUp var(--duration-exit-base) var(--ease-in-out) forwards;
}
Salidas ~20% más rápidas que entradas — la salida no necesita notarse.

Un ejemplo real: el funnel de suscripción

En Vocento — mayor grupo mediático de España, 15 marcas — rediseñé la experiencia del paywall de suscripción.

La primera versión tenía animaciones de entrada en cada elemento del paywall: el overlay, el modal, las tarjetas de precio, el CTA. Cada uno con su propia transición. La secuencia total de entrada duraba ~600ms.

Los usuarios que llegaban al paywall múltiples veces (que es el caso de usuarios no logueados en medios de noticias) experimentaban esos 600ms como resistencia, no como polish. Cada visita sentía como si el producto les hiciera esperar antes de pedirles dinero.

La solución fue eliminar la animación del paywall completamente. No reducirla — eliminarla.

El paywall aparece instantáneamente. El usuario ve la oferta de inmediato. La única animación que quedó fue el hover del CTA — 150ms, ease-out.

La conversión subió. No por haber eliminado la animación en sí, sino porque dejamos de crear fricción en el momento de mayor decisión del usuario.

La regla que emergió de ese proyecto:

Nunca animes elementos que están entre el usuario y su objetivo. Formularios, paywalls, checkouts, estados de error — existen para actuar sobre ellos, no para admirarlos.

Qué merece animación

El inverso: interacciones suficientemente raras para que el motion gane su lugar.

La primera vez que un usuario completa un flujo de varios pasos. Un toast confirmando una acción destructiva. Un momento de onboarding que solo ocurre una vez.

Estos ocurren una vez, quizás dos por sesión. El motion puede ser intencional sin acumular coste.

El checklist práctico

Antes de shiippear cualquier animación:

  1. ¿Cuántas veces al día lo triggerea un usuario? Más de 20 veces → eliminar o reducir a solo opacity
  2. ¿Lo inicia el teclado? Sí → sin animación, punto
  3. ¿Está entre el usuario y su objetivo? Sí (formulario, paywall, checkout) → sin animación
  4. ¿La duración está por debajo de 300ms? No → reducir
  5. ¿Existe el fallback de prefers-reduced-motion? No → añadirlo antes de shiipear
  6. ¿La animación comunica algo? No → eliminarla
/* Siempre. Sin excepciones. */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
Fallback de prefers-reduced-motion — añádelo antes que la animación.

La ausencia de motion es una decisión de diseño tan válida como su presencia. Saber cuándo parar es la habilidad más difícil.