Cómo combinar Tailwind CSS con animaciones personalizadas

Tailwind CSS se ha convertido en una herramienta muy popular para construir interfaces modernas de forma rápida, ordenada y flexible. Su enfoque basado en clases de utilidad permite diseñar componentes sin escribir grandes hojas de estilo desde cero. Pero cuando una interfaz necesita algo más que una buena estructura visual, entran en juego las animaciones.

Las animaciones no deberían usarse solo para “hacer bonito”. Bien aplicadas, ayudan a guiar la atención, mejorar la comprensión de una acción, suavizar cambios de estado y aportar una sensación más cuidada al producto digital. Por eso, aprender a combinar Tailwind CSS con animaciones personalizadas puede marcar una gran diferencia en la calidad final de una web o aplicación.

En este artículo veremos cómo trabajar con Tailwind CSS animaciones, cómo crear tus propios keyframes, cuándo conviene usar las utilidades nativas y cómo mantener un sistema de movimiento coherente, accesible y fácil de mantener.

Por qué usar animaciones en Tailwind CSS

Tailwind CSS ya incluye varias clases de animación listas para usar, como animate-spin, animate-ping, animate-pulse y animate-bounce. Estas utilidades son muy prácticas para resolver casos habituales, como loaders, indicadores de actividad, efectos de carga o pequeños elementos que necesitan llamar la atención.

Por ejemplo:

<div class="animate-spin"></div>
<div class="animate-pulse"></div>
<div class="animate-bounce"></div>

Estas clases pueden ser suficientes en muchos casos. Si estás creando un icono de carga, una flecha que invita a hacer scroll o un estado de espera sencillo, probablemente no necesites nada más.

El problema aparece cuando quieres que las animaciones formen parte de la identidad visual del proyecto. Una web profesional no siempre debería depender de los mismos efectos genéricos. Si todas las interfaces usan las mismas animaciones, el resultado puede sentirse poco personalizado.

Ahí es donde las animaciones personalizadas Tailwind se vuelven realmente útiles. Te permiten mantener la rapidez de Tailwind, pero añadiendo una capa propia de diseño de movimiento.

Si estás trabajando una estrategia más amplia de movimiento en CSS, también puede ayudarte revisar una base general sobre animaciones CSS, especialmente si quieres entender mejor la diferencia entre transiciones, keyframes y microinteracciones.

Animaciones nativas de Tailwind CSS: cuándo usarlas

Antes de crear animaciones personalizadas, conviene conocer bien las opciones que Tailwind ya ofrece. No siempre hace falta escribir @keyframes. De hecho, muchas interacciones sencillas se resuelven mejor con transiciones.

Transiciones para estados interactivos

Las transiciones funcionan muy bien cuando el cambio depende de una interacción del usuario: hover, focus, active, apertura de un menú o cambio de color.

Un ejemplo sencillo sería este botón:

<button class="rounded-lg bg-pink-600 px-5 py-3 text-white transition duration-300 ease-out hover:-translate-y-1 hover:bg-pink-700 hover:shadow-lg">
  Guardar cambios
</button>

Aquí no estamos usando una animación compleja. Solo estamos suavizando el cambio entre un estado normal y un estado hover. La clase transition indica que queremos animar el cambio, duration-300 controla la duración y ease-out aporta una salida más natural.

Este tipo de solución es ideal para botones, enlaces, tarjetas, iconos o pequeños elementos interactivos. Si quieres profundizar más en esta diferencia, puedes enlazar este tema con el artículo sobre transition vs animation en CSS, ya que es una duda muy habitual al empezar a trabajar con movimiento en interfaces.

Animaciones para secuencias temporales

Las animaciones, en cambio, son más adecuadas cuando necesitas una secuencia definida en el tiempo. Por ejemplo, un loader que gira, un skeleton que se ilumina, un aviso que aparece desde un lateral o una tarjeta que entra suavemente en pantalla.

Un ejemplo básico con Tailwind sería:

<span class="inline-flex h-3 w-3 animate-ping rounded-full bg-green-500"></span>

En este caso, el elemento no cambia porque el usuario haga hover. La animación ocurre de forma automática y repetida.

Como regla general, puedes pensar en esto: usa transiciones para cambios de estado y animaciones para secuencias de movimiento.

Cómo crear animaciones personalizadas en Tailwind

Para crear una animación personalizada necesitas dos partes: definir los @keyframes y crear una utilidad que puedas usar después en el HTML o en tus componentes.

En versiones modernas de Tailwind, una forma muy cómoda de hacerlo es definir variables de animación dentro de @theme.

Ejemplo básico con @keyframes

Imagina que quieres crear una animación de entrada suave para una tarjeta. El elemento debería aparecer desde abajo, con un pequeño desplazamiento vertical y un cambio progresivo de opacidad.

Puedes definirlo así:

@import "tailwindcss";

@theme {
  --animate-fade-up: fade-up 0.6s ease-out both;

  @keyframes fade-up {
    0% {
      opacity: 0;
      transform: translateY(1rem);
    }

    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

Después, puedes usar la clase animate-fade-up directamente en tu componente:

<article class="animate-fade-up rounded-2xl bg-white p-6 shadow-md">
  <h2 class="text-xl font-semibold">Tarjeta animada</h2>
  <p class="mt-2 text-gray-600">
    Esta tarjeta aparece con una animación personalizada.
  </p>
</article>

Este patrón es muy interesante porque mantiene las animaciones dentro del sistema visual del proyecto. En lugar de escribir clases aisladas para cada caso, creas una utilidad reutilizable que puedes aplicar en diferentes partes de la interfaz.

Cómo nombrar bien tus animaciones

Nombrar bien las animaciones es importante para mantener el proyecto ordenado. Evita nombres como animation-1, custom-effect o move-item, porque no explican qué hace la animación.

Es mejor usar nombres descriptivos como:

--animate-fade-up
--animate-fade-in
--animate-scale-in
--animate-slide-left
--animate-modal-in
--animate-shimmer

Un buen nombre debería ayudarte a entender el efecto sin tener que revisar los keyframes.

Nombres útiles para un sistema de diseño

Si trabajas con varios componentes, puedes crear una pequeña escala de movimiento:

@theme {
  --animate-enter-sm: enter-sm 0.3s ease-out both;
  --animate-enter-md: enter-md 0.45s ease-out both;
  --animate-enter-lg: enter-lg 0.6s ease-out both;

  @keyframes enter-sm {
    0% {
      opacity: 0;
      transform: translateY(0.5rem);
    }

    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }

  @keyframes enter-md {
    0% {
      opacity: 0;
      transform: translateY(1rem);
    }

    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }

  @keyframes enter-lg {
    0% {
      opacity: 0;
      transform: translateY(1.5rem);
    }

    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

Así puedes usar animaciones más sutiles para elementos pequeños y movimientos algo más visibles para secciones principales.

Usar animate-[…] con valores arbitrarios

Tailwind también permite usar valores arbitrarios con la clase animate-[...]. Esto puede ser muy útil cuando quieres probar una animación concreta sin crear una utilidad permanente.

Por ejemplo:

<div class="animate-[fade-up_0.6s_ease-out_both]">
  Contenido animado
</div>

Este enfoque es práctico para prototipos o casos puntuales, pero conviene no abusar. Si repites la misma animación varias veces, es mejor convertirla en una utilidad personalizada como animate-fade-up.

De esta forma, el código será más legible y fácil de mantener.

Ejemplos de animaciones personalizadas Tailwind

Ahora que ya tenemos la base, veamos varios ejemplos aplicados a componentes reales.

Animar una alerta

Una alerta necesita aparecer de forma clara, pero no agresiva. Una entrada lateral suave puede funcionar muy bien:

@theme {
  --animate-slide-alert: slide-alert 0.35s ease-out both;

  @keyframes slide-alert {
    0% {
      opacity: 0;
      transform: translateX(1rem);
    }

    100% {
      opacity: 1;
      transform: translateX(0);
    }
  }
}

Uso en HTML:

<div class="animate-slide-alert rounded-xl border border-green-200 bg-green-50 p-4 text-green-800">
  Los cambios se han guardado correctamente.
</div>

Este tipo de animación ayuda al usuario a detectar que algo ha cambiado en la interfaz. Es especialmente útil en mensajes de éxito, avisos, notificaciones o confirmaciones.

Animar un modal

Los modales suelen funcionar bien con una combinación de opacidad y escala. No hace falta que el movimiento sea exagerado; de hecho, cuanto más sutil, mejor.

@theme {
  --animate-modal-in: modal-in 0.25s ease-out both;

  @keyframes modal-in {
    0% {
      opacity: 0;
      transform: scale(0.96);
    }

    100% {
      opacity: 1;
      transform: scale(1);
    }
  }
}

Ejemplo de uso:

<div class="fixed inset-0 grid place-items-center bg-black/40">
  <div class="animate-modal-in w-full max-w-md rounded-2xl bg-white p-6 shadow-xl">
    <h2 class="text-lg font-semibold">Confirmar acción</h2>
    <p class="mt-2 text-gray-600">
      ¿Quieres continuar con esta operación?
    </p>
  </div>
</div>

La animación aporta contexto visual. El modal no aparece de golpe, sino que entra de forma más natural.

Crear un efecto shimmer para skeleton loaders

Los skeleton loaders ayudan a mejorar la percepción de carga mientras el contenido real todavía no está disponible. Puedes crear un efecto shimmer con una animación personalizada:

@theme {
  --animate-shimmer: shimmer 1.5s linear infinite;

  @keyframes shimmer {
    0% {
      background-position: -200% 0;
    }

    100% {
      background-position: 200% 0;
    }
  }
}

Y aplicarlo así:

<div class="h-4 w-full animate-shimmer rounded bg-[linear-gradient(90deg,#e5e7eb_25%,#f3f4f6_50%,#e5e7eb_75%)] bg-[length:200%_100%]"></div>

Este ejemplo conecta muy bien con el uso de skeleton loaders con CSS, ya que combina animación, percepción de rendimiento y experiencia de usuario.

Buenas prácticas para combinar Tailwind CSS y animaciones

Crear animaciones no consiste solo en mover elementos. Una buena animación debe ser clara, ligera, accesible y coherente con el resto de la interfaz.

Prioriza transform y opacity

Siempre que sea posible, anima propiedades como transform y opacity. Suelen ser más adecuadas para conseguir animaciones fluidas que propiedades como width, height, top, left o margin.

Por ejemplo, es preferible animar esto:

opacity: 0;
transform: translateY(1rem);

Antes que modificar directamente alturas, márgenes o posiciones que puedan afectar al layout.

Este punto es especialmente importante si buscas buen rendimiento. Puedes ampliar esta parte en el artículo sobre por qué animar transform y opacity antes que width o height, porque es una de las decisiones más importantes al diseñar movimiento en CSS.

No animes todo

Uno de los errores más comunes es animar demasiados elementos a la vez. Si cada botón, tarjeta, imagen, icono y bloque de texto se mueve constantemente, la interfaz pierde jerarquía.

La animación debería tener una intención clara. Puede servir para confirmar una acción, indicar carga, guiar la mirada o suavizar una transición. Pero si no aporta nada, probablemente sobra.

En este sentido, las animaciones deberían entenderse como parte de las microinteracciones con CSS: pequeños detalles que ayudan a que la interfaz responda mejor, no efectos decorativos sin propósito.

Controla la duración

La duración influye mucho en cómo se percibe una animación. Si es demasiado rápida, puede pasar desapercibida. Si es demasiado lenta, puede hacer que la interfaz parezca pesada.

Como referencia general:

  • Entre 150ms y 250ms funciona bien para microinteracciones.
  • Entre 300ms y 500ms puede funcionar bien para tarjetas, menús o modales.
  • Más de 700ms debería usarse con cuidado.

En Tailwind puedes controlar la duración con clases como:

<div class="transition duration-300"></div>

Y en una animación personalizada puedes definirla directamente:

--animate-fade-up: fade-up 0.6s ease-out both;

Usa easing con intención

El easing define cómo acelera o desacelera una animación. Una animación lineal puede sentirse mecánica, mientras que una animación con ease-out suele resultar más natural para entradas.

Por ejemplo:

--animate-modal-in: modal-in 0.25s ease-out both;

Para salidas, puedes usar ease-in. Para movimientos repetidos o decorativos, ease-in-out suele funcionar mejor.

Accesibilidad: respeta prefers-reduced-motion

No todas las personas disfrutan de las animaciones. Algunas pueden experimentar mareo, incomodidad o distracción con determinados movimientos. Por eso, al trabajar con Tailwind animation, es importante contemplar las preferencias de movimiento reducido.

Tailwind permite usar variantes como motion-safe y motion-reduce:

<div class="motion-safe:animate-fade-up motion-reduce:animate-none">
  Contenido accesible
</div>

Con este patrón, la animación solo se aplica cuando la preferencia del usuario lo permite. Si la persona ha configurado su sistema para reducir el movimiento, puedes evitar la animación o sustituirla por un cambio más discreto.

También puedes aplicarlo en botones:

<button class="transition hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100">
  Comprar ahora
</button>

La accesibilidad no significa eliminar toda personalidad visual. Significa ofrecer una experiencia más respetuosa. Si quieres desarrollar más este tema, puedes enlazarlo con el artículo sobre prefers-reduced-motion en CSS.

Cómo organizar animaciones en proyectos grandes

Cuando un proyecto crece, las animaciones pueden desordenarse muy rápido. Al principio quizá solo tengas dos o tres efectos, pero con el tiempo pueden aparecer variaciones para modales, alertas, menús, cards, loaders y componentes interactivos.

Para evitarlo, conviene definir una pequeña estrategia.

Crea una librería básica de movimiento

No necesitas veinte animaciones diferentes. Muchas veces basta con una colección pequeña y bien pensada:

@theme {
  --animate-fade-in: fade-in 0.3s ease-out both;
  --animate-fade-up: fade-up 0.45s ease-out both;
  --animate-scale-in: scale-in 0.25s ease-out both;
  --animate-shimmer: shimmer 1.5s linear infinite;

  @keyframes fade-in {
    0% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }

  @keyframes fade-up {
    0% {
      opacity: 0;
      transform: translateY(1rem);
    }

    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }

  @keyframes scale-in {
    0% {
      opacity: 0;
      transform: scale(0.96);
    }

    100% {
      opacity: 1;
      transform: scale(1);
    }
  }

  @keyframes shimmer {
    0% {
      background-position: -200% 0;
    }

    100% {
      background-position: 200% 0;
    }
  }
}

Esta pequeña librería ya cubre muchos casos: entradas suaves, apariciones simples, modales y cargas.

Documenta cuándo usar cada animación

Si trabajas en equipo o estás creando un sistema de diseño, documentar tus animaciones te ahorrará muchos problemas. Puedes indicar, por ejemplo:

  • animate-fade-in: apariciones simples.
  • animate-fade-up: tarjetas y bloques de contenido.
  • animate-scale-in: modales y popovers.
  • animate-shimmer: estados de carga.
  • animate-pulse: placeholders o indicadores sencillos.

Esta documentación ayuda a mantener consistencia visual y evita que cada componente tenga una animación distinta para resolver el mismo problema.

Tailwind keyframes en proyectos con configuración clásica

En algunos proyectos todavía se usa tailwind.config.js para extender el tema. En ese caso, puedes definir los keyframes y las animaciones dentro de theme.extend.

Un ejemplo sería:

export default {
  theme: {
    extend: {
      keyframes: {
        "fade-up": {
          "0%": {
            opacity: "0",
            transform: "translateY(1rem)",
          },
          "100%": {
            opacity: "1",
            transform: "translateY(0)",
          },
        },
      },
      animation: {
        "fade-up": "fade-up 0.6s ease-out both",
      },
    },
  },
};

Después podrías usar la clase así:

<div class="animate-fade-up">
  Contenido animado
</div>

La idea es la misma: defines la secuencia, le asignas un nombre y la conviertes en una utilidad reutilizable.

Si tu proyecto ya funciona con una configuración clásica, no siempre es necesario migrarlo todo de inmediato. Lo importante es mantener una arquitectura coherente y fácil de entender para quien tenga que tocar el código en el futuro.

Ejemplo completo: tarjeta animada con Tailwind CSS

Vamos a reunir varias ideas en un ejemplo completo. Imagina una tarjeta de artículo que aparece suavemente, tiene un hover discreto y respeta movimiento reducido.

Primero, definimos la animación:

@import "tailwindcss";

@theme {
  --animate-card-enter: card-enter 0.45s ease-out both;

  @keyframes card-enter {
    0% {
      opacity: 0;
      transform: translateY(0.75rem) scale(0.98);
    }

    100% {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
  }
}

Después, la aplicamos al componente:

<article class="motion-safe:animate-card-enter rounded-2xl border border-gray-200 bg-white p-6 shadow-sm transition duration-300 ease-out hover:-translate-y-1 hover:shadow-lg motion-reduce:animate-none motion-reduce:transition-none motion-reduce:hover:translate-y-0">
  <span class="text-sm font-medium text-pink-600">
    Tailwind CSS
  </span>

  <h2 class="mt-3 text-2xl font-semibold text-gray-950">
    Animaciones personalizadas con Tailwind
  </h2>

  <p class="mt-3 text-gray-600">
    Aprende a crear efectos reutilizables combinando utilidades, keyframes y buenas prácticas de accesibilidad.
  </p>

  <a class="mt-5 inline-flex font-medium text-pink-600 transition hover:text-pink-700" href="#">
    Leer más
  </a>
</article>

Este ejemplo combina una animación de entrada personalizada, una transición en hover, una estructura visual limpia y una adaptación básica para usuarios con movimiento reducido.

También muestra algo importante: una buena animación no necesita ser espectacular. A veces, un pequeño cambio de opacidad, desplazamiento y escala es suficiente para que una interfaz se sienta mucho más cuidada.

Errores comunes al crear animaciones personalizadas Tailwind

Crear animaciones demasiado específicas

Una animación llamada animate-home-section-card-special probablemente solo servirá para un caso concreto. Es mejor crear nombres más reutilizables, como animate-fade-up, animate-scale-in o animate-slide-alert.

Así podrás aplicar la misma animación en diferentes contextos sin duplicar código.

Abusar de animaciones infinitas

Las animaciones infinitas pueden ser útiles en loaders, indicadores de actividad o efectos de espera. Pero si un elemento se mueve constantemente sin aportar información, puede distraer.

Para estados de carga, puedes apoyarte en recursos como loaders animados solo con CSS, donde la repetición sí tiene una función clara.

Olvidar la intención del movimiento

Cada animación debería responder a una pregunta sencilla: ¿qué aporta esto a la experiencia?

Si la respuesta es “nada, solo se ve bonito”, quizá conviene reducirla o eliminarla. El movimiento debe ayudar a entender la interfaz, no competir con ella.

Preguntas frecuentes sobre Tailwind CSS animaciones

¿Puedo crear animaciones personalizadas en Tailwind sin escribir CSS adicional?

Puedes usar valores arbitrarios con animate-[...], pero normalmente necesitarás definir los @keyframes en algún lugar. Para proyectos reales, lo más recomendable es crear animaciones dentro del sistema de tema o configuración, porque así serán más reutilizables y fáciles de mantener.

¿Qué es mejor: usar animate-[…] o crear una utilidad personalizada?

Depende del caso. Si estás probando algo puntual, animate-[...] puede ser suficiente. Pero si vas a repetir la animación en varios componentes, es mejor crear una utilidad personalizada como animate-fade-up, animate-modal-in o animate-shimmer.

¿Las animaciones en Tailwind afectan al rendimiento?

Pueden afectar si se usan mal. Lo recomendable es animar propiedades como transform y opacity, evitar animaciones infinitas innecesarias, controlar la duración y respetar prefers-reduced-motion. Tailwind facilita la implementación, pero la calidad final depende de cómo diseñes el movimiento.

Reflexión final: animar con criterio también es diseñar mejor

Combinar Tailwind CSS con animaciones personalizadas permite crear interfaces más expresivas, coherentes y agradables de usar. Las utilidades de Tailwind te ayudan a trabajar rápido, mientras que los keyframes personalizados te dan el control necesario para construir una identidad visual propia.

Pero animar no significa mover elementos porque sí. Una buena animación debe tener intención. Puede guiar la mirada, confirmar una acción, suavizar una transición o comunicar que algo está cargando. Si no cumple ninguna de esas funciones, probablemente no sea necesaria.

Las animaciones personalizadas Tailwind son una herramienta muy potente cuando se usan con moderación. Te permiten mantener consistencia, mejorar la percepción de calidad y construir componentes más cuidados. Pero también exigen responsabilidad: pensar en rendimiento, accesibilidad y experiencia de usuario.

En definitiva, trabajar con Tailwind CSS animaciones no consiste solo en añadir clases como animate-fade-up o escribir unos cuantos Tailwind keyframes. Consiste en diseñar movimiento con propósito. Y cuando el movimiento tiene propósito, la interfaz no solo se ve mejor: también se entiende mejor.

Microcopy accesible: botones, mensajes de ayuda y textos de error

Cuando hablamos de accesibilidad digital solemos pensar en contraste, navegación mediante teclado, lectores de pantalla o HTML semántico. Todos estos aspectos son importantes, pero una interfaz técnicamente accesible puede seguir siendo difícil de utilizar si sus textos son ambiguos, demasiado técnicos o incompletos.

Aquí es donde entra en juego el microcopy accesible: los textos breves que orientan, informan y acompañan a las personas durante su interacción con una web o aplicación.

Botones, etiquetas, instrucciones, mensajes de ayuda, estados de carga, confirmaciones y textos de error forman parte del microcopy. Aunque ocupen poco espacio, tienen una responsabilidad considerable: explicar qué puede hacerse, qué información se necesita, qué está ocurriendo y cómo continuar.

Practicar un buen UX writing accesible no consiste únicamente en escribir frases cortas. También implica utilizar palabras comprensibles, ofrecer el contexto necesario, anticipar posibles dudas y redactar cada mensaje para que pueda entenderse sin depender exclusivamente de la presentación visual.

Qué es el microcopy accesible

El microcopy accesible es el conjunto de textos breves de una interfaz redactados para que las acciones, instrucciones y resultados sean comprensibles para el mayor número posible de personas.

Su objetivo no es simplificar todos los contenidos hasta eliminar cualquier matiz, sino reducir el esfuerzo necesario para interpretar la interfaz. Una persona debería poder responder con facilidad a estas preguntas:

  • ¿Qué puedo hacer en esta pantalla?
  • ¿Qué información tengo que introducir?
  • ¿Qué ocurrirá cuando active este botón?
  • ¿Se ha completado correctamente la acción?
  • ¿Qué debo corregir si aparece un error?

Cuando una interfaz no responde con claridad, obliga a deducir, recordar o probar distintas opciones. Esa incertidumbre aumenta la carga cognitiva y puede convertirse en una barrera especialmente importante para personas con dificultades de comprensión, memoria o atención.

La accesibilidad también depende de la comprensión

Una interfaz puede tener controles correctamente etiquetados y continuar ofreciendo una experiencia confusa.

Imaginemos un formulario que muestra el siguiente mensaje:

Error de validación. El parámetro introducido no cumple el formato esperado.

El sistema informa de que existe un problema, pero no explica qué campo debe revisarse ni cómo solucionarlo.

Una alternativa más útil sería:

Introduce el número de teléfono con nueve cifras, sin espacios.

La segunda versión identifica la solución y permite actuar sin necesidad de interpretar terminología técnica.

El microcopy accesible convierte las respuestas internas de un sistema en información comprensible para las personas.

Quién se beneficia de unos textos de interfaz más claros

La redacción accesible puede resultar especialmente importante para personas que:

  • utilizan lectores de pantalla o asistentes de voz;
  • presentan dislexia o dificultades de comprensión lectora;
  • tienen problemas de atención o memoria;
  • utilizan la interfaz en un idioma que no dominan completamente;
  • cuentan con poca experiencia digital;
  • navegan desde una pantalla pequeña;
  • se encuentran cansadas, preocupadas o bajo presión;
  • necesitan completar una tarea con rapidez.

Estas situaciones muestran que la accesibilidad no beneficia únicamente a un grupo concreto. Cualquier persona puede experimentar una limitación temporal o contextual.

Una interfaz clara reduce errores, facilita la toma de decisiones y genera más confianza.

Cómo escribir botones accesibles

Los botones representan acciones. Por tanto, su texto debería explicar qué ocurrirá cuando se activen.

Etiquetas como «Aceptar», «Continuar», «Enviar» o «Confirmar» pueden funcionar cuando el contexto es muy evidente, pero en muchos casos resultan demasiado genéricas. La persona debe recordar qué estaba aceptando, enviando o confirmando.

Utiliza verbos que describan acciones concretas

Un botón accesible suele comenzar con un verbo y expresar el resultado esperado.

En lugar de utilizar:

  • Aceptar.
  • Continuar.
  • Enviar.
  • Confirmar.
  • Sí.

Podemos escribir:

  • Guardar cambios.
  • Continuar al pago.
  • Enviar solicitud.
  • Confirmar reserva.
  • Eliminar archivo.

La segunda opción exige menos interpretación. Además, las etiquetas continúan teniendo sentido cuando se leen fuera del contexto visual de la pantalla.

Este criterio también debe aplicarse a los enlaces. Expresiones como «haz clic aquí» o «más información» pierden sentido cuando una persona navega directamente por la lista de enlaces de una página. En el artículo sobre cómo escribir enlaces accesibles y descriptivos puedes ampliar esta parte del trabajo de redacción.

Evita botones que dependan del texto situado alrededor

Una tarjeta puede incluir un título, una descripción y un botón llamado «Leer más». Visualmente, quizá parezca evidente qué contenido abre. Sin embargo, una persona que utiliza un lector de pantalla puede navegar directamente entre botones o enlaces y escuchar una sucesión como esta:

  • Leer más.
  • Leer más.
  • Leer más.

Sin el contenido circundante, las etiquetas dejan de ser útiles.

Una alternativa sería:

  • Leer más sobre accesibilidad web.
  • Leer más sobre testing frontend.
  • Leer más sobre diseño responsive.

La etiqueta debe conservar su significado aunque se lea de forma aislada.

Mantén la coherencia entre pantallas

Una misma acción no debería cambiar de nombre sin una razón clara.

Si el botón utilizado para conservar la información se llama «Guardar cambios», conviene mantener esa expresión en todo el producto. Alternar entre «Guardar», «Aplicar», «Actualizar» y «Confirmar» puede hacer pensar que cada botón realiza una acción diferente.

La coherencia verbal reduce el esfuerzo de aprendizaje y ayuda a reconocer patrones.

Botones para acciones delicadas

Las acciones que afectan a pagos, publicaciones o eliminación de información necesitan etiquetas especialmente precisas.

Por ejemplo:

  • Comprar por 29,90 €.
  • Publicar comentario.
  • Cancelar suscripción.
  • Cerrar sesión.
  • Eliminar cuenta definitivamente.

Cuando una acción no puede deshacerse, la interfaz debe indicarlo antes de ejecutarla.

En un cuadro de confirmación para eliminar un documento, es preferible mostrar:

  • Cancelar.
  • Eliminar documento.

En lugar de:

  • No.
  • Sí.

Los botones descriptivos evitan que la persona tenga que volver a leer toda la pregunta para recordar qué significa cada respuesta.

Qué ocurre con los botones que solo muestran un icono

Los iconos no siempre tienen un significado universal. Una papelera puede representar eliminar, vaciar o mover a la papelera. Un corazón puede significar guardar, reaccionar o añadir a favoritos.

Cuando un botón contiene únicamente un icono, necesita un nombre accesible que describa su función. Además, cuando el espacio lo permita, combinar icono y texto visible suele mejorar la comprensión.

Algunos ejemplos serían:

  • Icono de papelera y texto «Eliminar archivo».
  • Icono de flecha y texto «Descargar factura».
  • Icono de corazón y texto «Añadir a favoritos».

El nombre accesible debe describir la acción, no el aspecto del símbolo. «Icono de papelera» no explica qué ocurrirá al activar el botón.

Puedes profundizar en esta implementación en la guía sobre iconos sin texto, aria-hidden y nombres accesibles.

Cómo redactar mensajes de ayuda útiles

Los mensajes de ayuda ofrecen información adicional para completar una tarea. Pueden aclarar el formato esperado, explicar por qué se solicita un dato o advertir sobre las consecuencias de una elección.

Una ayuda accesible no debería limitarse a repetir la etiqueta del campo. Debe aportar información que permita actuar correctamente.

Anticipa las condiciones antes de que aparezca el error

Una interfaz no debería esperar a que la persona se equivoque para explicar las reglas.

Si una contraseña necesita ocho caracteres, una mayúscula y un número, estos requisitos deberían mostrarse antes de enviar el formulario:

La contraseña debe incluir al menos ocho caracteres, una mayúscula y un número.

Una ayuda como «La contraseña debe cumplir los requisitos de seguridad» no resulta suficiente porque obliga a descubrir las condiciones mediante ensayo y error.

Las instrucciones preventivas reducen errores y evitan que la persona tenga que repetir una tarea.

Coloca la ayuda junto al elemento correspondiente

El texto debe aparecer cerca del campo o control que explica. Además, la relación no puede ser únicamente visual: también debe existir en el código para que las tecnologías de asistencia puedan anunciar la ayuda en el momento apropiado.

Por ejemplo:

Fecha de nacimiento

Utiliza el formato día, mes y año. Por ejemplo, 18/07/1990.

La indicación explica el formato y ofrece un ejemplo concreto. No obliga a interpretar una abreviatura ni a recordar una convención.

La asociación técnica puede realizarse mediante atributos como aria-describedby, siempre que el contenido y el componente estén correctamente estructurados.

No utilices el placeholder como única etiqueta

El placeholder es el texto temporal que aparece dentro de un campo y desaparece cuando se empieza a escribir. No debería sustituir a una etiqueta visible ni contener instrucciones esenciales.

Depender únicamente de este recurso provoca varios problemas:

  • desaparece mientras se introduce la información;
  • puede confundirse con una respuesta ya escrita;
  • suele presentar un contraste reducido;
  • obliga a recordar la indicación;
  • no siempre se anuncia de forma consistente.

En lugar de colocar «Correo electrónico» únicamente dentro del campo, conviene utilizar una etiqueta visible y, cuando sea necesario, una ayuda adicional:

Correo electrónico

Utilizaremos esta dirección para enviarte la confirmación del pedido.

En la guía sobre formularios accesibles, etiquetas y validaciones encontrarás ejemplos técnicos para asociar correctamente etiquetas, campos y mensajes.

Explica por qué solicitas determinados datos

Cuando un formulario pide información personal, una explicación breve puede aumentar la confianza.

Por ejemplo:

Utilizaremos tu teléfono únicamente para avisarte de cambios en la entrega.

La persona entiende por qué se solicita el dato y puede tomar una decisión informada.

No es necesario añadir explicaciones extensas debajo de cada campo. La ayuda debe aparecer cuando resuelva una duda real o evite una posible preocupación.

Qué información debe permanecer visible

La información imprescindible para completar una tarea debe estar disponible desde el principio. Los iconos de interrogación, acordeones y textos desplegables pueden utilizarse para ampliar detalles, pero no deberían ocultar condiciones obligatorias.

Como regla general:

  • la información esencial debe permanecer visible;
  • la información complementaria puede desplegarse;
  • las explicaciones extensas pueden enlazar a una página específica.

Cómo redactar textos de error accesibles

Los mensajes de error aparecen cuando la persona ya se ha encontrado con un obstáculo. Por ello, deben ayudar a resolverlo, no limitarse a informar de que algo ha fallado.

Un texto de error útil responde a tres cuestiones:

  1. Qué ha ocurrido.
  2. Dónde está el problema.
  3. Cómo puede solucionarse.

Identifica el problema de forma precisa

Mensajes como «Algo ha salido mal», «Datos incorrectos» o «Error de validación» ofrecen muy poca información.

Una alternativa más útil sería:

No hemos podido guardar los cambios porque se ha perdido la conexión. Comprueba tu conexión a internet e inténtalo de nuevo.

El mensaje explica el problema probable y propone una acción.

Cuando el sistema no conoce la causa exacta, no debería inventarla. Puede comunicar únicamente la información confirmada:

No hemos podido completar el pago. No se ha realizado ningún cargo. Inténtalo de nuevo o utiliza otro método de pago.

Esta redacción informa del fallo, aclara una consecuencia importante y ofrece alternativas.

Señala qué campo necesita atención

En un formulario extenso, un aviso genérico situado al principio de la página no es suficiente. La persona puede saber que existen errores, pero no dónde encontrarlos.

Lo recomendable es combinar:

  • un resumen inicial;
  • un mensaje junto a cada campo afectado;
  • una señal visual que no dependa solo del color;
  • una asociación técnica entre el campo y el error.

Por ejemplo:

Revisa los dos campos indicados antes de continuar.

Junto al campo de correo electrónico:

Introduce una dirección válida, como nombre@ejemplo.com.

Evita culpar a la persona

Los mensajes deberían centrarse en el problema, no en juzgar a quien utiliza la interfaz.

En lugar de escribir:

Has introducido mal la contraseña.

Podemos utilizar:

La contraseña no coincide. Inténtalo de nuevo.

Otros ejemplos adecuados serían:

  • Falta completar el campo «Código postal».
  • El archivo supera el tamaño máximo de 10 MB.
  • La fecha debe ser posterior al 20 de julio de 2026.
  • El código ha caducado. Solicita uno nuevo.

El tono puede ser cercano, pero debe mantener la claridad y el respeto.

No dependas únicamente del color rojo

El color puede reforzar el estado de error, pero no debería ser su único indicador. Algunas personas no distinguen determinados colores y otras acceden al contenido mediante una presentación no visual.

Además del cambio cromático, pueden utilizarse:

  • la palabra «Error»;
  • un icono acompañado de texto;
  • un borde con una característica diferenciada;
  • un resumen de errores;
  • un mensaje descriptivo.

Por ejemplo:

Error: introduce una fecha posterior al 20 de julio de 2026.

El mensaje continúa siendo comprensible aunque el color no pueda percibirse.

Anuncia correctamente los errores dinámicos

En muchas aplicaciones, los errores aparecen sin recargar la página. Insertar visualmente un mensaje no garantiza que un lector de pantalla vaya a detectarlo.

Los cambios importantes deben anunciarse mediante una implementación adecuada, utilizando recursos como aria-live, role="alert" o role="status" según la prioridad y el tipo de mensaje.

El texto también debe poder entenderse al escucharse de forma independiente:

El código introducido ha caducado. Solicita un código nuevo.

Es mucho más informativo que anunciar únicamente:

No válido.

Para profundizar en los avisos dinámicos, puedes consultar la guía sobre toasts y notificaciones accesibles con aria-live.

Conserva la información que ya era correcta

Una buena experiencia de error no depende solo del mensaje. Si el formulario elimina todos los datos debido a un único campo incorrecto, la persona tendrá que repetir un trabajo que ya había completado.

Siempre que sea seguro, la interfaz debería conservar las respuestas válidas, identificar únicamente los campos problemáticos y situar el foco en una posición lógica.

Principios generales de UX writing accesible

Botones, ayudas y errores cumplen funciones diferentes, pero todos deberían seguir unos principios comunes.

Utiliza lenguaje directo

Las frases breves y directas suelen comprenderse mejor que las construcciones administrativas o excesivamente técnicas.

En lugar de:

Para proceder a la finalización del proceso de registro será necesaria la validación previa de la dirección de correo electrónico proporcionada.

Podemos escribir:

Revisa tu correo y confirma la dirección para completar el registro.

La segunda versión conserva la información y reduce el esfuerzo de lectura.

Coloca la información importante al principio

Las personas no siempre leen una interfaz palabra por palabra. Con frecuencia, escanean el contenido para localizar la información relevante.

Por eso conviene comenzar por el resultado principal:

Pago rechazado. Utiliza otra tarjeta o consulta con tu banco.

Es preferible a una frase larga que revele el problema al final.

Sustituye la terminología interna

Los códigos y términos utilizados por el equipo técnico no deberían aparecer como explicación principal.

Es preferible utilizar:

  • Número de pedido en lugar de ID de transacción.
  • Dirección de entrega en lugar de domicilio de expedición.
  • Guardar borrador en lugar de persistir contenido.
  • Página no encontrada en lugar de error HTTP 404.

El código técnico puede mostrarse como información secundaria cuando sea útil para soporte, pero no debe sustituir a la explicación.

Evita instrucciones basadas en la posición

Indicaciones como «pulsa el botón de la derecha», «selecciona la opción inferior» o «revisa los campos en rojo» dependen de la presentación visual.

La disposición puede cambiar en móvil, al ampliar el contenido o al navegar con tecnologías de asistencia.

Es preferible nombrar directamente el elemento:

  • Selecciona «Guardar cambios».
  • Revisa el campo «Correo electrónico».
  • Abre la sección «Datos de facturación».

No utilices el humor para ocultar información

Un tono cercano puede formar parte de la identidad de una marca, pero el humor debe utilizarse con prudencia en situaciones relacionadas con pagos, privacidad, pérdida de información o errores.

Un mensaje como «¡Vaya, la hemos liado!» puede resultar simpático, pero no explica qué ha ocurrido.

Una alternativa con personalidad y utilidad sería:

No hemos podido publicar el artículo. El borrador está guardado, así que puedes intentarlo de nuevo.

La voz de marca puede acompañar al mensaje, pero nunca reemplazar la información necesaria.

Cómo revisar el microcopy antes de publicar

La revisión de los textos debería realizarse dentro del flujo completo. Una etiqueta que parece clara en una hoja de cálculo puede perder sentido cuando aparece junto a otras acciones.

Recorre las tareas principales

Prueba los procesos más importantes de la interfaz:

  • crear una cuenta;
  • iniciar sesión;
  • recuperar una contraseña;
  • completar un formulario;
  • realizar una compra;
  • guardar o eliminar información;
  • cancelar una operación;
  • introducir datos incorrectos;
  • utilizar la aplicación sin conexión.

Durante la revisión, comprueba si cada pantalla explica qué está ocurriendo y qué opciones existen.

Lee botones y enlaces fuera de contexto

Extrae una lista con todas las etiquetas interactivas e intenta comprenderlas sin observar el diseño.

Si aparecen muchas expresiones como «Más», «Aceptar», «Continuar» o «Aquí», probablemente necesiten una revisión.

Esta prueba se aproxima a la experiencia de una persona que utiliza un lector de pantalla para navegar directamente entre controles.

Provoca errores deliberadamente

Los estados de error suelen recibir menos atención que el recorrido ideal. Para comprobarlos, introduce:

  • campos vacíos;
  • formatos incorrectos;
  • contraseñas que no coinciden;
  • archivos demasiado grandes;
  • fechas no permitidas;
  • datos duplicados;
  • códigos caducados.

Cada error debería identificar el problema y ofrecer una solución posible.

Comprueba la experiencia con teclado

El texto de un botón puede ser claro, pero la experiencia seguirá siendo inaccesible si el control no puede alcanzarse o si el foco no resulta visible.

Conviene completar los principales recorridos utilizando únicamente Tab, Mayús + Tab, Enter, barra espaciadora y Escape. La guía sobre foco visible y navegación por teclado puede ayudarte a revisar esta parte de la interacción.

Crea una guía de estilo para los textos de interfaz

Una guía breve facilita la coherencia y evita resolver los mismos problemas en cada pantalla.

Puede definir:

  • tono de voz;
  • tratamiento de tú o usted;
  • terminología preferida;
  • verbos utilizados en botones;
  • estructura de errores y confirmaciones;
  • criterios de mayúsculas y puntuación;
  • formato de fechas, horas y cantidades;
  • expresiones que deben evitarse;
  • requisitos básicos de accesibilidad.

Checklist de microcopy accesible

Antes de aprobar un texto, comprueba:

  1. ¿Describe claramente la acción o el estado?
  2. ¿Se entiende sin depender del diseño?
  3. ¿Utiliza palabras familiares para la audiencia?
  4. ¿Explica cómo solucionar el problema?
  5. ¿Evita culpar a la persona?
  6. ¿Mantiene la terminología utilizada en otras pantallas?
  7. ¿La información esencial aparece antes de realizar la acción?
  8. ¿El texto visible coincide con el nombre accesible?
  9. ¿Se entiende al escucharlo fuera de contexto?
  10. ¿Puede simplificarse sin perder información?

Preguntas frecuentes sobre microcopy accesible

¿Cuál es la diferencia entre microcopy y UX writing?

El microcopy está formado por los textos breves que aparecen dentro de una interfaz: botones, etiquetas, mensajes de ayuda, errores y confirmaciones.

El UX writing es una disciplina más amplia que planifica y redacta el contenido necesario para guiar a las personas a lo largo de toda la experiencia digital. El microcopy es, por tanto, una parte del UX writing.

Cuando hablamos de UX writing accesible, incorporamos criterios de comprensión, inclusión, coherencia y compatibilidad con tecnologías de asistencia.

¿Un botón con un icono necesita texto?

Necesita, como mínimo, un nombre accesible que identifique su función. El icono puede acompañarse de texto visible o disponer de una etiqueta accesible para lectores de pantalla.

Siempre que el espacio y el diseño lo permitan, combinar icono y texto suele ser más comprensible. La etiqueta debe describir la acción, como «Descargar factura», y no la apariencia del símbolo.

¿Cómo debe redactarse un buen mensaje de error?

Un buen texto de error debe indicar qué ha ocurrido, dónde se encuentra el problema y cómo solucionarlo.

También debería utilizar un tono neutral, evitar tecnicismos innecesarios y conservar los datos que ya eran correctos.

Por ejemplo:

El código postal debe contener cinco cifras. Revisa el campo «Código postal» e inténtalo de nuevo.

Este mensaje resulta mucho más útil que una expresión genérica como «Datos no válidos».

Las palabras también pueden eliminar barreras

La accesibilidad digital no termina cuando una interfaz puede recorrerse con el teclado o cuando sus componentes incluyen los atributos técnicos adecuados.

Una experiencia solo es verdaderamente accesible cuando las personas pueden comprender qué deben hacer, reconocer qué está ocurriendo y recuperarse cuando aparece un problema.

Los botones deben describir acciones. Los mensajes de ayuda tienen que anticipar dudas. Los textos de error deben ofrecer una salida, no limitarse a señalar un fallo.

Escribir microcopy accesible requiere precisión, empatía y colaboración entre diseño, contenido y desarrollo. También exige probar los textos en situaciones reales, escuchar cómo se anuncian mediante tecnologías de asistencia y observar si las instrucciones se entienden sin ayuda externa.

Una sola palabra ambigua puede impedir una compra, bloquear un formulario o provocar la pérdida de confianza. Del mismo modo, una indicación clara puede evitar un error y permitir que alguien complete una tarea con autonomía.

Cada texto de interfaz es una oportunidad para eliminar una barrera. Cuando redactamos pensando en diferentes capacidades, experiencias y contextos, no solo mejoramos la accesibilidad: creamos productos más comprensibles, seguros y humanos.

Cómo animar elementos al hacer scroll sin JavaScript

Animar elementos al hacer scroll sin JavaScript es una de esas técnicas que hace unos años parecía depender casi siempre de librerías externas, detectores de intersección, escuchas de eventos o soluciones personalizadas con scroll. Sin embargo, CSS ha avanzado mucho, y hoy podemos crear animaciones vinculadas al desplazamiento de una página de forma mucho más declarativa, limpia y cercana al propio lenguaje de estilos.

Cuando hablamos de animaciones scroll CSS sin JavaScript, no nos referimos únicamente a “hacer aparecer cosas con un poco de opacidad”. Hablamos de barras de progreso, textos que entran suavemente en pantalla, imágenes que se revelan al hacer scroll, secciones que se desplazan con efecto parallax moderado y componentes que reaccionan al avance del usuario dentro de una página.

La gran ventaja es clara: podemos mejorar la experiencia visual sin añadir complejidad innecesaria al proyecto. Eso sí, también conviene ser realistas. Las animaciones basadas en scroll con CSS moderno son muy potentes, pero todavía requieren una estrategia de compatibilidad progresiva. En otras palabras: podemos usarlas, pero debemos prepararlas para que la web siga funcionando correctamente aunque el navegador no soporte todas las propiedades más recientes.

En este artículo vamos a ver cómo animar al hacer scroll con CSS, qué propiedades modernas entran en juego, cuándo conviene usarlas, cómo plantear alternativas seguras y qué buenas prácticas deberías aplicar para que tus animaciones sean bonitas, accesibles y sostenibles.

Qué significa animar al hacer scroll sin JavaScript

Animar al hacer scroll significa que una animación no depende únicamente del paso del tiempo, como ocurre con una animación CSS tradicional. En una animación clásica, defines unos @keyframes, asignas una duración y el navegador reproduce la animación durante un número determinado de segundos.

Por ejemplo:

.card {
  animation: fade-in 0.8s ease both;
}

@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(24px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Este código anima la tarjeta cuando se carga o cuando aparece en el flujo normal del documento, pero no está realmente vinculado al scroll. La animación ocurre por tiempo.

En cambio, una scroll animation CSS conecta el progreso de la animación con el desplazamiento del usuario. Es decir, la animación avanza, retrocede o se completa según la posición de scroll.

Esto permite crear efectos como:

  • Barras de lectura que se llenan al avanzar por un artículo.
  • Elementos que aparecen cuando entran en el viewport.
  • Imágenes que se revelan progresivamente.
  • Títulos que se desplazan o escalan de forma suave.
  • Secciones que ganan opacidad a medida que el usuario baja.

La diferencia principal es conceptual: ya no pensamos en una duración fija, sino en una relación entre posición de scroll y progreso de animación.

La base técnica: CSS Scroll-Driven Animations

Las animaciones al hacer scroll sin JavaScript se apoyan en lo que se conoce como CSS Scroll-Driven Animations. Este conjunto de propiedades permite asociar una animación CSS a una línea temporal basada en el desplazamiento.

En lugar de utilizar únicamente la línea temporal tradicional del documento, CSS puede usar una línea temporal derivada del scroll. Esto abre la puerta a animaciones declarativas, sin necesidad de escuchar eventos scroll desde JavaScript.

Scroll timeline y view timeline

Dentro de este enfoque hay dos ideas importantes:

Scroll timeline

Una scroll timeline vincula la animación al progreso de desplazamiento de un contenedor. Es útil, por ejemplo, para crear una barra de progreso de lectura que se rellena desde el inicio hasta el final de la página.

En este caso, la animación responde al avance general del scroll.

View timeline

Una view timeline vincula la animación a la visibilidad de un elemento dentro del viewport. Es decir, el elemento puede empezar a animarse cuando entra en pantalla y completar la animación cuando alcanza una determinada zona visible.

Este enfoque es especialmente útil para animar elementos al hacer scroll CSS sin tener que detectar manualmente si el elemento está visible.

Ejemplo básico: animar una tarjeta al entrar en pantalla

Vamos a partir de un ejemplo sencillo. Imagina una serie de tarjetas dentro de una página de servicios, portfolio o blog. Queremos que cada tarjeta aparezca suavemente cuando el usuario llega a esa zona.

HTML de ejemplo

<section class="features">
  <article class="feature-card">
    <h2>Diseño responsive</h2>
    <p>Interfaces adaptadas a cualquier tamaño de pantalla.</p>
  </article>

  <article class="feature-card">
    <h2>Animaciones suaves</h2>
    <p>Microinteracciones cuidadas para mejorar la experiencia.</p>
  </article>

  <article class="feature-card">
    <h2>CSS moderno</h2>
    <p>Soluciones visuales limpias sin depender siempre de JavaScript.</p>
  </article>
</section>

CSS con animation-timeline: view()

.feature-card {
  opacity: 0;
  transform: translateY(40px);
  animation: reveal-card linear both;
  animation-timeline: view();
  animation-range: entry 10% cover 35%;
}

@keyframes reveal-card {
  from {
    opacity: 0;
    transform: translateY(40px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Con este código, cada tarjeta se anima en relación con su entrada en pantalla. La propiedad animation-timeline: view() indica que la animación dependerá de la visibilidad del propio elemento.

La propiedad animation-range define en qué tramo de esa visibilidad empieza y termina la animación. En este caso, la tarjeta comienza a animarse cuando entra parcialmente en pantalla y completa su transición cuando ya ocupa una parte más clara del viewport.

Este patrón es ideal para crear una sensación de progresión sin convertir la página en un espectáculo excesivo. La animación acompaña al contenido, no compite con él.

Barra de progreso de lectura solo con CSS

Una de las aplicaciones más útiles de las animaciones vinculadas al scroll es la barra de progreso de lectura. Es habitual verla en artículos largos, guías técnicas o páginas de documentación.

La idea es sencilla: una barra fija en la parte superior de la página se va rellenando a medida que el usuario avanza.

HTML

<div class="reading-progress"></div>

CSS

.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999;
  width: 100%;
  height: 4px;
  transform-origin: left;
  transform: scaleX(0);
  animation: reading-progress linear both;
  animation-timeline: scroll(root);
}

@keyframes reading-progress {
  to {
    transform: scaleX(1);
  }
}

Aquí usamos animation-timeline: scroll(root), que vincula la animación al scroll del documento raíz. La barra empieza con scaleX(0) y termina con scaleX(1), creando una sensación de avance visual muy clara.

Este tipo de detalle mejora la orientación del usuario. En artículos extensos, una barra de progreso puede ayudar a entender cuánto contenido queda por leer sin añadir textos, porcentajes ni componentes adicionales.

Animaciones de revelado con imagen y texto

Otra técnica muy interesante consiste en revelar imágenes o bloques visuales a medida que entran en pantalla. Este efecto puede funcionar muy bien en portfolios, landing pages, páginas de producto o artículos visuales.

HTML

<figure class="image-reveal">
  <img src="proyecto-web.jpg" alt="Vista previa de un proyecto web responsive">
</figure>

CSS

.image-reveal {
  overflow: hidden;
}

.image-reveal img {
  display: block;
  width: 100%;
  transform: scale(1.15);
  opacity: 0;
  animation: reveal-image linear both;
  animation-timeline: view();
  animation-range: entry 15% cover 45%;
}

@keyframes reveal-image {
  to {
    transform: scale(1);
    opacity: 1;
  }
}

En este caso combinamos opacity y transform, dos propiedades especialmente recomendables para animaciones porque suelen ser más eficientes que modificar dimensiones, márgenes o posiciones que obliguen al navegador a recalcular el layout.

El resultado es una imagen que aparece suavemente mientras reduce su escala. Es un efecto elegante, fácil de entender y suficientemente discreto para no saturar la experiencia.

Hacer que el texto acompañe al movimiento

También podemos aplicar una animación similar a los textos:

.scroll-title {
  opacity: 0;
  transform: translateY(32px);
  animation: reveal-title linear both;
  animation-timeline: view();
  animation-range: entry 20% cover 40%;
}

@keyframes reveal-title {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Este patrón funciona muy bien en encabezados de sección. La clave está en no abusar. Si cada párrafo, imagen, botón y tarjeta de una página se anima, la experiencia puede volverse cansada. La animación debe tener una intención: guiar, jerarquizar o reforzar una transición visual.

Compatibilidad: cómo usar CSS moderno sin romper la experiencia

Aquí conviene hacer una pausa importante. Aunque las animaciones de scroll con CSS son una herramienta muy prometedora, no deberías construir una experiencia que dependa al cien por cien de ellas.

La mejor estrategia es aplicar mejora progresiva. Es decir, primero defines un diseño funcional y accesible para todos los navegadores. Después, añades la animación solo cuando el navegador soporte las propiedades necesarias.

Usar @supports

Podemos envolver nuestras animaciones modernas dentro de una regla @supports:

.feature-card {
  opacity: 1;
  transform: none;
}

@supports (animation-timeline: view()) {
  .feature-card {
    opacity: 0;
    transform: translateY(40px);
    animation: reveal-card linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 35%;
  }
}

Este enfoque es mucho más seguro. Por defecto, la tarjeta se muestra normalmente. Si el navegador soporta animation-timeline: view(), entonces se aplica la animación.

Esto evita uno de los errores más peligrosos: dejar elementos con opacity: 0 en navegadores que no soportan la animación. Si eso ocurre, el contenido podría quedar invisible para parte de los usuarios.

Orden correcto de las propiedades

Un detalle importante: si usas la propiedad abreviada animation, declara animation-timeline después. Algunas propiedades abreviadas pueden reiniciar valores relacionados con la animación, por lo que conviene mantener un orden claro:

.element {
  animation: reveal linear both;
  animation-timeline: view();
}

Evita hacer esto:

.element {
  animation-timeline: view();
  animation: reveal linear both;
}

Aunque parezca un detalle menor, este tipo de orden puede marcar la diferencia entre una animación que funciona y otra que no se aplica como esperabas.

Buenas prácticas para animar al hacer scroll con CSS

Animar con scroll no consiste en añadir movimiento porque sí. Una buena animación tiene una función. Debe aportar claridad, ritmo o contexto.

Prioriza opacity y transform

Cuando sea posible, anima propiedades como:

  • opacity
  • transform: translate
  • transform: scale
  • transform: rotate

Estas propiedades suelen ser más adecuadas para animaciones fluidas porque no modifican directamente el flujo del documento.

En cambio, conviene evitar animar propiedades como:

  • width
  • height
  • margin
  • top
  • left
  • padding

No significa que estén prohibidas, pero pueden generar más trabajo de renderizado y afectar al rendimiento si se usan en exceso.

No conviertas cada scroll en una feria visual

Una página llena de animaciones puede parecer moderna durante los primeros segundos, pero cansar rápidamente. Lo recomendable es elegir puntos clave:

  • La entrada de una sección importante.
  • Una imagen destacada.
  • Una barra de progreso.
  • Un bloque de llamada a la acción.
  • Una transición entre partes del contenido.

El movimiento debe acompañar al usuario, no interrumpirlo.

Cuida la duración percibida

En las animaciones scroll CSS sin JavaScript, la duración no se mide igual que en una animación temporal. Depende del tramo de scroll asignado. Por eso animation-range es tan importante.

Un rango demasiado corto puede hacer que el efecto sea brusco. Un rango demasiado largo puede hacer que la animación parezca lenta o imprecisa.

Una buena regla inicial es probar rangos como:

animation-range: entry 10% cover 35%;

O bien:

animation-range: entry 20% cover 50%;

Después ajusta según el tipo de elemento, el ritmo de la página y la importancia visual del contenido.

Accesibilidad: respeta prefers-reduced-motion

No todas las personas disfrutan de las animaciones. Para algunos usuarios, ciertos movimientos pueden resultar molestos, distraer o incluso provocar incomodidad. Por eso es fundamental respetar la preferencia del sistema mediante prefers-reduced-motion.

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

También puedes ser más específica y desactivar solo las animaciones vinculadas al scroll:

@media (prefers-reduced-motion: reduce) {
  .feature-card,
  .image-reveal img,
  .scroll-title {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

Este segundo enfoque suele ser más controlado. Permite mantener la interfaz estable y evita que el contenido dependa del movimiento para ser comprensible.

La accesibilidad no debería verse como una capa final, sino como parte del diseño de la animación desde el principio.

CSS sin JavaScript no significa CSS sin estrategia

Usar CSS sin JavaScript para animaciones de scroll tiene muchas ventajas. Reduce dependencias, simplifica el mantenimiento y permite escribir efectos visuales de forma más declarativa. Pero eso no significa que debamos aplicarlo sin criterio.

Hay casos en los que CSS será suficiente:

  • Efectos de aparición simples.
  • Barras de progreso.
  • Revelados visuales.
  • Animaciones decorativas no críticas.
  • Microinteracciones asociadas a la visibilidad del elemento.

Pero también hay casos en los que JavaScript puede seguir siendo necesario:

  • Animaciones con lógica compleja.
  • Estados dependientes de datos.
  • Sincronización con componentes interactivos.
  • Experiencias donde la animación modifica comportamiento real.
  • Compatibilidad estricta con navegadores antiguos.

La clave está en elegir la herramienta adecuada. Si el efecto es visual, progresivo y no crítico, CSS puede ser una opción excelente. Si la animación forma parte de una lógica más compleja, quizá JavaScript siga teniendo sentido.

Ejemplo completo: sección animada al hacer scroll

Veamos un ejemplo más completo que combina buenas prácticas, mejora progresiva y accesibilidad.

HTML

<section class="services-section">
  <header class="section-header">
    <p class="eyebrow">Servicios web</p>
    <h2>Interfaces modernas con CSS limpio y eficiente</h2>
    <p>
      Creamos experiencias visuales cuidadas, accesibles y adaptadas
      a diferentes dispositivos.
    </p>
  </header>

  <div class="services-grid">
    <article class="service-card">
      <h3>Diseño responsive</h3>
      <p>Componentes pensados para funcionar en móvil, tablet y escritorio.</p>
    </article>

    <article class="service-card">
      <h3>Animaciones CSS</h3>
      <p>Microinteracciones suaves que mejoran la experiencia sin sobrecargarla.</p>
    </article>

    <article class="service-card">
      <h3>Optimización visual</h3>
      <p>Interfaces rápidas, claras y alineadas con los objetivos del proyecto.</p>
    </article>
  </div>
</section>

CSS

.services-section {
  padding: 6rem 1.5rem;
}

.section-header,
.service-card {
  opacity: 1;
  transform: none;
}

.services-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.service-card {
  padding: 1.5rem;
  border-radius: 1rem;
  background: #ffffff;
  box-shadow: 0 16px 40px rgb(0 0 0 / 0.08);
}

@supports (animation-timeline: view()) {
  .section-header {
    opacity: 0;
    transform: translateY(32px);
    animation: reveal-block linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 35%;
  }

  .service-card {
    opacity: 0;
    transform: translateY(40px);
    animation: reveal-block linear both;
    animation-timeline: view();
    animation-range: entry 15% cover 40%;
  }
}

@keyframes reveal-block {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .section-header,
  .service-card {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

Este ejemplo tiene tres puntos importantes. Primero, el contenido es visible por defecto. Segundo, la animación solo se aplica si el navegador la soporta. Tercero, se respeta la preferencia de reducción de movimiento.

Es una base mucho más sólida que limitarse a copiar una animación vistosa sin pensar en compatibilidad, accesibilidad o mantenimiento.

Errores comunes al crear animaciones scroll CSS sin JavaScript

Aunque la técnica es relativamente sencilla, hay varios errores frecuentes que conviene evitar.

Dejar el contenido oculto por defecto

Si defines opacity: 0 fuera de @supports, puedes provocar que el contenido quede invisible en navegadores sin soporte. Es mejor que el estado base sea visible y que la animación se añada como mejora.

Animar demasiadas propiedades

Una animación que combina opacidad, escala, desplazamiento, blur, rotación y cambios de tamaño puede resultar pesada y confusa. Cuanto más simple sea el movimiento, más profesional suele sentirse.

No probar en dispositivos reales

Las animaciones pueden verse muy bien en escritorio y resultar incómodas en móvil. El ritmo de scroll, el tamaño del viewport y el rendimiento cambian mucho según el dispositivo.

Ignorar la accesibilidad

No respetar prefers-reduced-motion es un error importante. Las animaciones deben poder reducirse o eliminarse sin que la página pierda contenido ni sentido.

FAQs sobre animar elementos al hacer scroll sin JavaScript

¿Se puede animar al hacer scroll solo con CSS?

Sí, actualmente CSS permite crear animaciones vinculadas al scroll mediante propiedades como animation-timeline, scroll() y view(). Esto permite construir efectos visuales sin escuchar eventos de scroll con JavaScript. Aun así, conviene aplicar mejora progresiva porque no todos los navegadores ofrecen el mismo nivel de soporte.

¿Es mejor usar CSS o JavaScript para animaciones de scroll?

Depende del caso. Para efectos visuales sencillos, revelados de contenido, barras de progreso o animaciones decorativas, CSS puede ser una solución más limpia y mantenible. Para interacciones complejas, estados dinámicos o lógica avanzada, JavaScript puede seguir siendo necesario.

¿Las animaciones de scroll afectan al rendimiento?

Pueden afectar si se usan mal. Para mejorar el rendimiento, conviene animar propiedades como transform y opacity, evitar animaciones excesivas y probar en dispositivos reales. También es importante no depender de animaciones críticas para mostrar contenido esencial.

Reflexión final: animar menos, pero animar mejor

Animar elementos al hacer scroll sin JavaScript es una posibilidad muy atractiva para quienes trabajamos con CSS moderno. Nos permite crear experiencias más dinámicas, reducir dependencias y aprovechar mejor las capacidades nativas del navegador.

Sin embargo, la pregunta importante no es solo “¿puedo animarlo?”, sino “¿esta animación mejora realmente la experiencia?”. Una buena animación no debería estar para presumir de técnica, sino para acompañar la lectura, reforzar la jerarquía visual y hacer que la interfaz resulte más clara.

Las animaciones scroll CSS sin JavaScript tienen mucho potencial, especialmente cuando se combinan con accesibilidad, mejora progresiva y sentido del diseño. Usadas con moderación, pueden transformar una página estática en una experiencia más fluida y memorable. Usadas sin criterio, pueden convertirse en ruido.

Por eso, la mejor recomendación es empezar de forma sencilla: una barra de progreso, una tarjeta que aparece suavemente, una imagen que se revela al entrar en pantalla. A partir de ahí, prueba, mide, ajusta y elimina todo lo que no aporte valor.

En CSS, como en diseño, muchas veces la elegancia está en saber cuándo moverse y cuándo quedarse quieto.