Cómo crear animaciones suaves con transition

Crear una interfaz agradable no depende solo de elegir buenos colores, una tipografía legible o una estructura clara. También influye mucho cómo responde la web cuando una persona interactúa con ella. Un botón que cambia de estado de forma brusca puede sentirse rígido. En cambio, un botón que cambia de color o se desplaza ligeramente con suavidad transmite una sensación más cuidada y profesional.

Aquí es donde entra en juego transition, una de las propiedades más útiles de CSS para crear pequeñas animaciones entre dos estados. Con ella podemos suavizar cambios de color, movimientos, escalados, opacidades o sombras sin necesidad de utilizar JavaScript ni crear animaciones complejas con @keyframes.

Si estás empezando a trabajar con movimiento en interfaces, te recomiendo leer también esta guía sobre animaciones CSS desde cero, donde explico las bases para entender cuándo usar transition, cuándo usar animation y qué propiedades conviene animar.

En este artículo vamos a centrarnos en cómo crear animaciones suaves con transition, cómo escribir su sintaxis correctamente, qué errores evitar y cómo aplicarla en botones, enlaces, tarjetas, menús e imágenes.

Qué es transition en CSS

La propiedad transition permite que un cambio entre dos estados de un elemento no ocurra de golpe, sino de forma progresiva.

Por ejemplo, imagina un botón con un color de fondo rosa. Cuando pasas el cursor por encima, ese botón cambia a morado. Sin transición, el cambio es inmediato. Con transition, el navegador genera los estados intermedios y el cambio se percibe de manera más suave.

.button {
  background-color: #cc2b5e;
  transition: background-color 0.3s ease;
}

.button:hover {
  background-color: #753a88;
}

En este ejemplo, el cambio de color tarda 0.3s y utiliza una curva de movimiento ease. Es un detalle pequeño, pero cambia mucho la percepción de la interfaz.

La idea principal es sencilla: transition suaviza el paso de un valor CSS a otro. No crea una animación compleja por sí sola, sino que anima un cambio que ya existe.

Por eso suele utilizarse en estados como:

  • :hover
  • :focus
  • :active
  • clases añadidas con JavaScript
  • cambios visuales en componentes interactivos

Es una propiedad especialmente útil para microinteracciones: botones, enlaces, tarjetas, menús, iconos, tooltips o pequeños efectos visuales.

Diferencia entre transition y animation

Una duda muy habitual es cuándo conviene usar transition y cuándo es mejor usar animation.

Aunque ambas sirven para crear movimiento en CSS, no tienen el mismo propósito.

transition se utiliza cuando queremos animar el cambio entre dos estados. Por ejemplo, un botón en reposo y ese mismo botón en estado hover.

.card {
  transform: translateY(0);
  transition: transform 0.25s ease;
}

.card:hover {
  transform: translateY(-6px);
}

Aquí solo hay dos estados: la tarjeta en su posición inicial y la tarjeta ligeramente elevada. Este es un caso perfecto para usar transition.

En cambio, animation se utiliza cuando necesitamos una secuencia más elaborada, varios pasos intermedios o una repetición. Por ejemplo, un loader girando, un icono latiendo en bucle o una animación de entrada con diferentes fases.

Regla práctica para elegir

Puedes quedarte con esta idea:

Si hay dos estados, usa transition.
Si hay una secuencia o repetición, usa animation.

Para un efecto hover, lo normal es usar transition. Para un loader infinito, probablemente usarás @keyframes y animation.

Esta diferencia es importante porque muchas veces se usa animation para resolver efectos que podrían hacerse de forma más simple, limpia y mantenible con transition.

Sintaxis de transition

La propiedad transition es una abreviatura que agrupa varias propiedades relacionadas con la transición.

Su sintaxis habitual es:

transition: propiedad duración función-de-tiempo retraso;

Por ejemplo:

transition: transform 0.3s ease-in-out 0s;

Este código indica que la propiedad transform debe cambiar durante 0.3s, con una curva ease-in-out y sin retraso.

También se puede escribir de forma separada:

.element {
  transition-property: transform;
  transition-duration: 0.3s;
  transition-timing-function: ease-in-out;
  transition-delay: 0s;
}

Ambas formas son correctas, pero en proyectos reales suele utilizarse la versión abreviada porque es más compacta y fácil de leer.

transition-property: qué propiedad se anima

transition-property indica qué propiedad CSS queremos animar.

transition-property: background-color;

También podemos animar varias propiedades separándolas con comas:

transition-property: background-color, transform;

Una opción muy común es usar all:

transition: all 0.3s ease;

Aunque puede parecer cómodo, conviene tener cuidado. all indica que cualquier propiedad que cambie será animada. Esto puede provocar resultados inesperados si más adelante modificas otras propiedades del elemento.

Por eso, en muchos casos es mejor indicar exactamente qué propiedades quieres animar:

transition: background-color 0.3s ease, transform 0.3s ease;

El código queda más claro, más predecible y más fácil de mantener.

transition-duration: cuánto dura la transición

transition-duration define cuánto tarda la transición en completarse. Puede expresarse en segundos o milisegundos.

transition-duration: 300ms;

O también:

transition-duration: 0.3s;

Para microinteracciones, como botones o enlaces, suelen funcionar bien valores entre 150ms y 300ms.

Si la transición es demasiado rápida, apenas se nota. Si es demasiado lenta, puede hacer que la interfaz se sienta pesada.

.button {
  transition: background-color 180ms ease, transform 180ms ease;
}

Este tipo de duración suele funcionar muy bien para botones porque aporta suavidad sin hacer esperar a la persona usuaria.

transition-timing-function: cómo se mueve la transición

La función de tiempo define cómo progresa la transición durante su duración. Es decir, si empieza rápido, termina lento, mantiene velocidad constante o combina aceleración y desaceleración.

Algunos valores habituales son:

transition-timing-function: ease;
transition-timing-function: linear;
transition-timing-function: ease-in;
transition-timing-function: ease-out;
transition-timing-function: ease-in-out;

El valor ease suele funcionar bien en muchos casos. Sin embargo, para interfaces más cuidadas, ease-out y ease-in-out pueden dar una sensación más natural.

Por ejemplo:

.card {
  transition: transform 0.25s ease-out;
}

ease-out hace que el movimiento empiece con más energía y termine suavemente. Es muy útil para tarjetas que se elevan, elementos que aparecen o pequeños desplazamientos.

transition-delay: retrasar el inicio

transition-delay permite retrasar el inicio de la transición.

transition-delay: 0.1s;

Puede ser útil cuando quieres crear un efecto escalonado, por ejemplo en una lista de elementos que aparecen de forma progresiva.

.item {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.item.is-visible {
  opacity: 1;
  transform: translateY(0);
}

El retraso puede aportar ritmo visual, pero conviene usarlo con moderación. Si todo tarda demasiado en responder, la experiencia puede sentirse lenta.

Cómo crear un hover suave con transition

Uno de los usos más habituales de transition es crear un hover suave en botones, enlaces o tarjetas.

Veamos un ejemplo sencillo:

.button {
  display: inline-block;
  padding: 0.85rem 1.4rem;
  border-radius: 999px;
  background-color: #cc2b5e;
  color: #ffffff;
  text-decoration: none;
  transition: background-color 0.25s ease, transform 0.25s ease;
}

.button:hover {
  background-color: #753a88;
  transform: translateY(-2px);
}

Aquí ocurren dos cosas: cambia el color de fondo y el botón se desplaza ligeramente hacia arriba.

El resultado es una interacción sencilla, pero más fluida y agradable que un cambio instantáneo.

Añadir también el estado focus

Un error frecuente es diseñar solo el estado :hover y olvidarse de las personas que navegan con teclado.

Por eso, siempre que tenga sentido, conviene añadir también :focus-visible:

.button:hover,
.button:focus-visible {
  background-color: #753a88;
  transform: translateY(-2px);
}

Así el botón responde tanto al cursor como al foco de teclado. Es un pequeño detalle que mejora la accesibilidad y hace que la interfaz sea más coherente.

Si este tema te interesa, también puedes conectar este tipo de decisiones con el diseño de interacciones y prototipos. En esta guía sobre cómo pasar de wireframe a prototipo interactivo en Figma explico cómo pensar mejor los estados y flujos antes de llevarlos al código.

Propiedades recomendadas para animaciones suaves CSS

No todas las propiedades CSS se animan igual de bien. Algunas obligan al navegador a recalcular el layout de la página, mientras que otras son más eficientes.

Para conseguir animaciones suaves, normalmente conviene priorizar:

  • transform
  • opacity

Con transform podemos mover, escalar, rotar o inclinar un elemento sin modificar directamente el flujo del documento.

.card:hover {
  transform: scale(1.03);
}

Con opacity podemos crear efectos de aparición y desaparición:

.tooltip {
  opacity: 0;
  transition: opacity 0.2s ease;
}

.tooltip.is-visible {
  opacity: 1;
}

Mejor transform que top, left o margin

Aunque se pueden animar propiedades como width, height, margin, padding, top o left, no siempre es recomendable. Estos cambios pueden afectar al layout y hacer que la animación sea menos fluida.

Por ejemplo, en lugar de mover un elemento con top:

.box:hover {
  top: -10px;
}

es preferible usar transform:

.box:hover {
  transform: translateY(-10px);
}

El resultado visual puede ser parecido, pero la segunda opción suele ser más estable y eficiente.

Ejemplos prácticos de CSS transitions

A continuación tienes varios ejemplos que puedes adaptar a tus propios proyectos.

Enlace con cambio de color suave

.link {
  color: #cc2b5e;
  text-decoration: none;
  transition: color 0.2s ease;
}

.link:hover,
.link:focus-visible {
  color: #753a88;
}

Este patrón funciona muy bien en enlaces dentro de artículos, menús o llamadas a la acción secundarias.

Tarjeta que se eleva al pasar el cursor

.card {
  padding: 1.5rem;
  border-radius: 1rem;
  background-color: #ffffff;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  transform: translateY(0);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.14);
}

Este efecto es muy útil en tarjetas de servicios, entradas destacadas del blog o proyectos de portfolio. La clave está en que el movimiento sea sutil. No hace falta desplazar mucho el elemento para que se entienda que es interactivo.

Imagen con zoom suave

.image-wrapper {
  overflow: hidden;
  border-radius: 1rem;
}

.image-wrapper img {
  display: block;
  width: 100%;
  transition: transform 0.4s ease;
}

.image-wrapper:hover img {
  transform: scale(1.06);
}

El contenedor tiene overflow: hidden para evitar que la imagen se salga visualmente al escalar. Es un recurso muy usado en galerías, cards de blog y proyectos visuales.

Menú desplegable con opacidad y desplazamiento

.dropdown {
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.dropdown.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

Este ejemplo es útil cuando el estado del menú se controla con una clase, por ejemplo mediante JavaScript.

Si estás trabajando con rutas, navegación o enlaces internos en proyectos React, también puede interesarte esta guía sobre React Router Hash Link y enlaces ancla en React, donde el comportamiento de navegación puede combinarse con detalles visuales como el desplazamiento suave.

Buenas prácticas para usar transition

Una transición bien aplicada puede mejorar mucho la experiencia de usuario. Pero si se usa sin intención, también puede generar ruido o distracción.

Usa transiciones con un propósito

No todos los elementos necesitan moverse. La animación debe ayudar a entender mejor una acción, no convertirse en un adorno constante.

Antes de añadir una transición, pregúntate:

  • ¿Ayuda a comprender mejor la interacción?
  • ¿Hace que el cambio de estado sea más claro?
  • ¿Refuerza la acción de la persona usuaria?
  • ¿Puede resultar molesta si se repite muchas veces?

Una buena transición suele sentirse natural. Está ahí, mejora la experiencia, pero no roba protagonismo.

Mantén duraciones cortas

Para botones, enlaces e iconos, normalmente basta con una duración entre 150ms y 300ms.

Para elementos más grandes, como modales, paneles o menús desplegables, puedes usar valores algo mayores, como 300ms o 400ms.

.modal {
  transition: opacity 0.3s ease, transform 0.3s ease;
}

La transición debe acompañar la experiencia, no ralentizarla.

Evita abusar de transition: all

Aunque transition: all parece una solución rápida, puede generar efectos inesperados.

transition: all 0.3s ease;

Es mejor indicar las propiedades concretas:

transition: background-color 0.3s ease, transform 0.3s ease;

Así el código es más explícito y fácil de mantener.

Respeta la preferencia de movimiento reducido

No todas las personas viven el movimiento en pantalla de la misma forma. Algunas pueden sentirse incómodas con animaciones excesivas.

Por eso conviene tener en cuenta prefers-reduced-motion:

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

Esto reduce al mínimo las animaciones para quienes han indicado en su sistema que prefieren menos movimiento.

Errores comunes al usar transition

Aunque transition es sencilla, hay varios errores habituales que pueden hacer que no funcione como esperas.

Poner la transición solo en :hover

Este es uno de los errores más comunes:

.button:hover {
  background-color: #753a88;
  transition: background-color 0.3s ease;
}

El problema es que la transición solo se declara en el estado hover. Lo recomendable es definirla en el estado base:

.button {
  background-color: #cc2b5e;
  transition: background-color 0.3s ease;
}

.button:hover {
  background-color: #753a88;
}

Así el navegador puede animar tanto la entrada como la salida del estado.

Intentar animar display

No se puede animar de forma fluida un cambio de display: none a display: block.

Esto no generará una transición suave:

.menu {
  display: none;
  transition: display 0.3s ease;
}

.menu.is-open {
  display: block;
}

Una alternativa más adecuada es combinar opacity, transform, visibility y pointer-events:

.menu {
  opacity: 0;
  transform: translateY(10px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
}

.menu.is-open {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
}

Crear movimientos demasiado exagerados

Una transición no tiene que ser espectacular para funcionar. De hecho, muchas veces cuanto más sutil, mejor.

Por ejemplo, una tarjeta que se desplaza 40px puede sentirse exagerada. En cambio, un desplazamiento de 4px, 6px u 8px suele ser suficiente.

.card:hover {
  transform: translateY(-6px);
}

La suavidad no depende solo de la duración. También depende de la distancia, la curva de movimiento y el contexto.

Cómo combinar varias transiciones

Puedes aplicar varias transiciones a un mismo elemento separándolas con comas.

.button {
  background-color: #cc2b5e;
  color: #ffffff;
  transform: translateY(0);
  transition:
    background-color 0.25s ease,
    color 0.25s ease,
    transform 0.25s ease;
}

.button:hover {
  background-color: #753a88;
  color: #ffffff;
  transform: translateY(-2px);
}

También puedes usar duraciones diferentes para cada propiedad:

.card {
  transition:
    transform 0.25s ease-out,
    box-shadow 0.35s ease;
}

En este caso, el movimiento de la tarjeta es un poco más rápido que el cambio de sombra. Son detalles pequeños, pero ayudan a que la interfaz se sienta más cuidada.

Cómo organizar tus transiciones en proyectos reales

Cuando una web crece, no conviene escribir duraciones y curvas distintas en cada componente sin ningún criterio. Lo ideal es crear cierta coherencia visual.

Una buena práctica es definir variables CSS:

:root {
  --transition-fast: 150ms ease;
  --transition-base: 250ms ease;
  --transition-slow: 400ms ease;
}

Después puedes reutilizarlas:

.button {
  transition: background-color var(--transition-base), transform var(--transition-base);
}

Esto ayuda a mantener una experiencia consistente en toda la interfaz.

También puedes crear utilidades reutilizables:

.u-transition {
  transition: transform 0.25s ease, opacity 0.25s ease;
}

Eso sí, conviene no abusar de clases genéricas si estás trabajando con componentes muy específicos. Lo importante es que el sistema sea claro y fácil de mantener.

Si además estás guardando preferencias visuales en el navegador, como modo claro, modo oscuro o estados personalizados, puedes complementar este tema con el artículo sobre cómo usar localStorage y sessionStorage en proyectos JavaScript.

Preguntas frecuentes sobre transition CSS

¿Qué es transition en CSS?

transition es una propiedad de CSS que permite suavizar el cambio entre dos estados de un elemento. Por ejemplo, puede hacer que un botón cambie de color de forma progresiva al pasar el cursor por encima.

Se utiliza mucho para crear animaciones suaves en botones, enlaces, tarjetas, menús, imágenes y otros elementos interactivos.

¿Cuál es la diferencia entre transition y animation?

transition sirve para animar el cambio entre dos estados, como normal y hover. animation, en cambio, permite crear secuencias más complejas mediante @keyframes, repeticiones o varios pasos intermedios.

Si necesitas suavizar un cambio simple, usa transition. Si necesitas una secuencia, usa animation.

¿Qué propiedades conviene animar para conseguir transiciones suaves?

Las propiedades más recomendadas suelen ser transform y opacity, porque permiten crear efectos visuales fluidos sin modificar directamente el layout.

Por ejemplo, para mover un elemento es mejor usar transform: translateY() que cambiar top o margin. Para mostrar u ocultar algo suavemente, suele ser mejor usar opacity que intentar animar display.

Cuando una pequeña transición mejora toda la experiencia

Crear animaciones suaves con transition no consiste en llenar una web de efectos. Consiste en diseñar cambios de estado que ayuden a entender mejor lo que ocurre en la interfaz.

Un botón que responde con suavidad confirma una acción. Una tarjeta que se eleva ligeramente indica que puede interactuarse con ella. Un menú que aparece de forma gradual evita una sensación brusca. Un enlace que cambia de color de manera fluida hace que la navegación se sienta más cuidada.

La clave está en usar transition CSS con intención: elegir bien qué propiedad animar, cuánto debe durar, qué curva de movimiento encaja mejor y cómo afecta esa decisión a la accesibilidad y al rendimiento.

En definitiva, las CSS transitions son una herramienta sencilla, potente y muy útil para mejorar la calidad percibida de una web. No necesitas crear grandes efectos para que una interfaz se sienta más profesional. A veces, una transición de 200ms, aplicada en el lugar adecuado, marca la diferencia entre una experiencia rígida y una experiencia fluida, clara y agradable.

Qué partes de CSS funcionan realmente en email marketing

Cuando venimos del desarrollo web, maquetar un email puede sentirse como volver varios años atrás. En una web actual damos por hecho que podemos usar Flexbox, Grid, variables CSS, animaciones, fuentes externas, pseudo-elementos, componentes interactivos y media queries avanzadas. Sin embargo, en email marketing la pregunta no es “¿puedo escribir este CSS?”, sino algo bastante más práctico: ¿lo van a interpretar correctamente Gmail, Outlook, Apple Mail, Yahoo y otros clientes de correo?

Y aquí empieza el verdadero reto. El CSS en email existe, funciona y es necesario, pero no funciona con la misma libertad que en una página web. Por eso, cuando hablamos de css email, css compatible email o css outlook, en realidad estamos hablando de compatibilidad, pruebas y decisiones de diseño mucho más conservadoras.

Un email no se renderiza en un navegador universal. Se abre en muchos clientes distintos, cada uno con sus propias reglas, filtros y limitaciones. Lo que se ve perfecto en Apple Mail puede romperse en Outlook. Lo que funciona en Gmail móvil puede comportarse de forma diferente en un webmail corporativo. Por eso, para diseñar emails eficaces, conviene pensar menos como si estuviéramos creando una landing page y más como si estuviéramos construyendo una pieza de comunicación resistente.

Si ya has trabajado con maquetación de emails o estás empezando a explorar herramientas como MJML, te puede resultar útil complementar este artículo con la guía sobre qué es MJML y por qué facilita la maquetación de emails responsive, donde explico cómo este framework ayuda a reducir parte de la complejidad técnica.

Por qué CSS en email marketing no funciona igual que en una web

En desarrollo web, el navegador interpreta HTML y CSS siguiendo estándares bastante consistentes. Puede haber diferencias entre Chrome, Safari, Firefox o Edge, pero en general existe una base común razonablemente estable. En email marketing, en cambio, la situación es bastante más irregular.

Cada cliente de correo puede modificar, filtrar o ignorar partes del HTML y del CSS. Algunos aceptan estilos en la etiqueta <style>. Otros los procesan de forma parcial. Algunos respetan propiedades como border-radius, mientras que otros las ignoran en determinados contextos. Y luego está Outlook, que durante años ha sido uno de los grandes dolores de cabeza para quienes maquetan emails.

El problema no es solo CSS, sino el motor de renderizado

Una de las razones por las que el CSS en email es tan impredecible está en el motor que utiliza cada cliente para mostrar el mensaje. Apple Mail, por ejemplo, suele ofrecer buen soporte porque se apoya en WebKit. En cambio, algunas versiones clásicas de Outlook para Windows han usado Microsoft Word como motor de renderizado, lo que explica muchas de sus limitaciones con propiedades modernas de CSS.

Esto significa que un mismo email puede verse muy bien en un cliente y romperse en otro. No necesariamente porque el código esté mal escrito, sino porque el entorno donde se abre el email no interpreta CSS como lo haría un navegador moderno.

Por eso, cuando diseñamos una campaña de email marketing, no basta con abrir el archivo HTML en el navegador y comprobar que se ve bien. Esa prueba solo nos dice cómo lo interpreta el navegador, no cómo lo interpretará Outlook, Gmail, Apple Mail o un cliente de correo corporativo.

La mentalidad correcta: diseñar para resistir

En email marketing conviene trabajar con una lógica de degradación elegante. Es decir, el email puede verse más bonito en clientes modernos, pero debe seguir siendo legible, claro y funcional en clientes más restrictivos.

No pasa nada si una sombra decorativa no aparece. Tampoco es grave si un borde redondeado se muestra como un rectángulo. Lo importante es que el mensaje se entienda, que el botón principal sea visible y que la jerarquía visual siga funcionando.

La pregunta clave sería: si se eliminan los efectos decorativos, ¿el email sigue cumpliendo su función? Si la respuesta es sí, vamos por buen camino.

La base más fiable: tablas, atributos HTML y CSS inline

Aunque pueda sonar anticuado, la estructura más fiable para un email sigue estando basada en tablas HTML. No porque las tablas sean mejores desde el punto de vista semántico, sino porque muchos clientes de correo las interpretan de forma más estable que los layouts modernos con div, Flexbox o Grid.

En email marketing, las tablas no se usan para representar datos, sino para controlar la estructura visual. Es una práctica heredada, sí, pero todavía muy útil cuando necesitamos que una newsletter se vea correctamente en distintos entornos.

Si estás comparando enfoques de maquetación, también puedes revisar el artículo sobre MJML vs HTML tradicional para emails, donde explico las ventajas y limitaciones de trabajar directamente con HTML frente a usar una herramienta que abstrae parte del código.

Qué significa usar CSS inline en email

Usar CSS inline significa aplicar los estilos directamente sobre cada elemento HTML. En una web esto sería poco recomendable por mantenimiento, escalabilidad y separación de responsabilidades. En email, sin embargo, sigue siendo una de las prácticas más seguras.

<td style="font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px; color: #333333;">
  Texto del email
</td>

La razón es sencilla: algunos clientes de correo eliminan o modifican estilos declarados en el <head>. En cambio, los estilos inline tienen más posibilidades de sobrevivir al procesamiento del cliente.

Esto no significa que todo el CSS deba ir siempre inline y que no podamos usar clases. Significa que los estilos esenciales deben estar lo más cerca posible del elemento que los necesita. Por ejemplo: tamaño de texto, color, fuente, espaciado básico, fondo y estilos principales de botones.

Cuándo sí tiene sentido usar estilos en la etiqueta style

Aunque el CSS inline es la base más segura, los estilos dentro de <style> siguen teniendo utilidad. Pueden utilizarse para media queries, ajustes responsive, modo oscuro, clases auxiliares o mejoras progresivas para clientes modernos.

La diferencia está en no depender exclusivamente de ellos. Una buena regla práctica sería esta: lo esencial va inline; lo complementario puede ir en <style>.

Por ejemplo, un botón debe seguir pareciendo un botón aunque una media query no se aplique. Una columna debe seguir mostrando el contenido aunque no se apile exactamente como esperábamos. Y un bloque de texto debe seguir siendo legible aunque no cargue la fuente personalizada.

Propiedades CSS que suelen funcionar bien en email

A pesar de las limitaciones, hay muchas propiedades CSS que sí funcionan de manera bastante fiable en email marketing. La clave está en usarlas con sentido común y evitar que el diseño dependa de propiedades demasiado modernas o frágiles.

Tipografía básica

Las propiedades relacionadas con texto suelen ser de las más seguras. Entre ellas encontramos:

  • font-family
  • font-size
  • font-weight
  • line-height
  • color
  • text-align
  • text-decoration
  • text-transform

Estas propiedades son fundamentales para construir jerarquía visual. Permiten diferenciar títulos, subtítulos, párrafos, enlaces y llamadas a la acción sin depender de estructuras complejas.

Eso sí, conviene usar fuentes de sistema o definir buenos fallbacks. Las fuentes externas pueden funcionar en algunos clientes, pero no en todos. Por eso, si una marca utiliza una tipografía personalizada, lo más prudente es acompañarla siempre de alternativas seguras:

style="font-family: 'MiFuente', Arial, Helvetica, sans-serif;"

De esta manera, si la fuente principal no carga, el email seguirá viéndose correctamente.

Colores, fondos simples y bordes

También suelen funcionar bien propiedades como color, background-color, border, padding, width o vertical-align. Son propiedades sencillas, pero muy útiles para construir una experiencia visual clara.

El uso de background-color es especialmente importante porque permite crear bloques visuales sin depender de imágenes. En email marketing, siempre conviene que el diseño funcione aunque las imágenes no carguen. Por eso, un botón con color de fondo y texto real suele ser más robusto que una imagen que contiene el texto del botón.

Este principio también está muy relacionado con la accesibilidad. Si el mensaje principal está dentro de una imagen y esa imagen no se carga, el usuario puede quedarse sin información clave. En cambio, si el texto es real, el contenido sigue estando disponible.

Espaciado con padding

El padding suele ser más fiable que el margin, especialmente cuando se aplica sobre celdas <td>. Por ejemplo:

<td style="padding: 24px 32px;">
  Contenido del bloque
</td>

En cambio, confiar demasiado en márgenes puede producir resultados inconsistentes, sobre todo en Outlook. Por eso, en emails complejos se suele controlar el espaciado mediante tablas, celdas, atributos y padding.

No es tan limpio como trabajar con CSS moderno, pero es mucho más previsible.

Propiedades CSS que funcionan, pero con reservas

Hay un segundo grupo de propiedades que pueden funcionar bien en muchos clientes, pero no conviene tratarlas como base estructural. Son útiles para mejorar el acabado visual, pero no deberían sostener la comprensión del email.

Border-radius

border-radius se utiliza mucho para botones, tarjetas o imágenes redondeadas. En muchos clientes funciona correctamente, pero en otros puede fallar o requerir soluciones específicas.

La recomendación práctica es sencilla: puedes usar border-radius, pero el email no debería depender de él. Si un botón pierde las esquinas redondeadas, debe seguir pareciendo un botón. Si una tarjeta se muestra con esquinas rectas, el contenido debe seguir siendo claro.

En otras palabras, los bordes redondeados son una mejora visual, no una garantía estructural.

Box-shadow

Las sombras pueden aportar profundidad y separar visualmente bloques, pero no son una propiedad especialmente fiable en email. Algunos clientes las muestran correctamente, otros las ignoran y otros pueden modificar su comportamiento tras una actualización.

Por eso, si quieres usar box-shadow, hazlo como recurso decorativo. No lo utilices como único mecanismo para diferenciar una tarjeta del fondo. Es mejor combinarlo con un color de fondo, un borde suave o un espaciado claro.

Así, si la sombra no aparece, el diseño no se rompe.

Media queries

Las media queries son muy útiles para adaptar newsletters a móvil. Permiten ajustar tamaños de texto, apilar columnas, modificar anchos o mejorar zonas táctiles.

Sin embargo, no todos los clientes de correo las soportan igual. Por eso, es recomendable diseñar emails que ya sean legibles sin depender por completo de media queries. Una estructura híbrida, fluida o mobile-first suele ser más segura que un diseño rígido que solo funciona si se aplican todos los estilos responsive.

Si estás empezando con este tipo de maquetación, puedes ampliar esta parte con la guía sobre cómo crear tu primera newsletter responsive con MJML, especialmente si buscas una forma más sencilla de trabajar con columnas, secciones y comportamiento móvil.

Modo oscuro

El modo oscuro es uno de los puntos más delicados en email marketing. Algunos clientes respetan tus colores, otros los modifican automáticamente y otros invierten parcialmente fondos, textos o imágenes.

Esto puede provocar problemas de contraste, logotipos que se ven mal o botones que pierden claridad. Por eso, conviene definir colores de fondo explícitos, probar imágenes en claro y oscuro, evitar textos sobre imágenes sin fallback y revisar siempre el email en varios clientes.

El modo oscuro no debe tratarse como un detalle menor. Cada vez más usuarios lo tienen activado por defecto, así que ignorarlo puede afectar directamente a la legibilidad de una campaña.

CSS que conviene evitar en email marketing

Ahora viene la parte más importante cuando hablamos de CSS compatible con email: qué conviene evitar o, al menos, no usar como base del diseño.

Flexbox y Grid como estructura principal

Flexbox y Grid son dos herramientas fundamentales en desarrollo web moderno, pero en email marketing no son la opción más segura para la estructura principal.

Pueden funcionar en algunos clientes, especialmente en entornos más modernos, pero su soporte no es suficientemente uniforme como para utilizarlos como base de una newsletter que debe verse bien en Gmail, Outlook, Apple Mail y webmails variados.

La recomendación práctica es clara: usa tablas para la estructura principal y reserva Flexbox o Grid para casos muy controlados, siempre que hayas validado previamente el soporte en los clientes más importantes para tu audiencia.

Si vienes de trabajar mucho con layouts web, puede que esta limitación resulte frustrante. Pero en email marketing la prioridad no es usar la técnica más moderna, sino conseguir que el mensaje llegue de forma estable.

Position, float y layouts complejos

Propiedades como position: absolute, position: fixed, float o z-index pueden generar comportamientos imprevisibles en email. En una web permiten crear interfaces ricas y composiciones complejas. En un email, en cambio, suelen aumentar el riesgo de roturas.

Un email no necesita comportarse como una aplicación. Necesita comunicar una idea, mantener la identidad visual de la marca y dirigir a una acción clara. Cuanto más complejo sea el layout, más probable será que algo falle en algún cliente.

Pseudo-elementos y selectores avanzados

Selectores como :hover, :focus, :nth-child, combinadores complejos o pseudo-elementos como ::before y ::after no deberían formar parte de la base de un email comercial.

En desarrollo web son recursos muy útiles. De hecho, si te interesa este enfoque visual, en el blog también puedes leer sobre pseudo-elementos en CSS y cómo ayudan a crear ilustraciones más complejas. Pero en email marketing conviene ser mucho más prudente.

Si necesitas una decoración visual importante, probablemente sea mejor resolverla con HTML real, una imagen optimizada o una estructura más simple.

Variables CSS, animaciones y funciones modernas

Las custom properties, calc(), clamp(), filtros, máscaras, animaciones, transiciones o efectos avanzados pueden funcionar en algunos clientes, pero no deberían ser necesarios para una campaña de email marketing convencional.

El problema no es que estén “prohibidos”, sino que añaden incertidumbre. Y en email, la incertidumbre visual suele traducirse en pérdida de control sobre la experiencia final.

Cuando dudes, aplica esta regla: si una propiedad mejora el email pero no es imprescindible, puedes planteártela como mejora progresiva. Si una propiedad es imprescindible para que el email se entienda, debería ser muy compatible.

Outlook: el gran filtro de compatibilidad

Cuando se habla de CSS en Outlook, es importante aclarar que Outlook no es un único entorno. Existe Outlook clásico para Windows, el nuevo Outlook para Windows, Outlook.com, Outlook para Mac y las apps móviles de Outlook. Y no todos se comportan igual.

El problema histórico está sobre todo en Outlook clásico para Windows. Sus limitaciones han hecho que muchas plantillas de email tengan que incorporar soluciones específicas, comentarios condicionales o incluso VML para ciertos fondos y botones.

Qué suele romperse en Outlook

En Outlook conviene revisar especialmente:

  • anchos de imágenes;
  • fondos con imágenes;
  • espaciados verticales inesperados;
  • líneas blancas entre bloques;
  • botones con bordes redondeados;
  • columnas;
  • sombras y efectos decorativos;
  • fuentes personalizadas;
  • márgenes.

Esto no significa que debamos diseñar emails feos o excesivamente básicos. Significa que debemos diseñar con una base robusta y dejar los detalles más delicados como mejoras visuales.

Cómo trabajar mejor con Outlook

Para mejorar la compatibilidad con Outlook, conviene seguir algunas buenas prácticas:

  • usar tablas para la estructura principal;
  • definir anchos de imágenes con cuidado;
  • aplicar CSS inline en elementos clave;
  • evitar depender de margin para el espaciado principal;
  • crear botones con texto real;
  • probar el email antes del envío;
  • asumir que algunos efectos visuales no se mostrarán igual.

No se trata de diseñar únicamente para Outlook, sino de evitar que Outlook destruya la experiencia básica del mensaje.

Ejemplo de estructura segura para un bloque de email

Un bloque de email relativamente seguro podría tener una estructura como esta:

<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: #f5f5f5;">
  <tr>
    <td align="center" style="padding: 32px 16px;">
      <table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 600px; background-color: #ffffff;">
        <tr>
          <td style="padding: 32px; font-family: Arial, Helvetica, sans-serif; color: #222222;">
            <h1 style="margin: 0 0 16px; font-size: 28px; line-height: 34px; font-weight: bold;">
              Título principal
            </h1>
            <p style="margin: 0 0 24px; font-size: 16px; line-height: 24px;">
              Texto introductorio del email con una estructura sencilla y compatible.
            </p>
            <table role="presentation" cellspacing="0" cellpadding="0" border="0">
              <tr>
                <td style="background-color: #cc2b5e; padding: 12px 24px;">
                  <a href="https://ejemplo.com" style="font-family: Arial, Helvetica, sans-serif; font-size: 16px; color: #ffffff; text-decoration: none; display: inline-block;">
                    Ver más
                  </a>
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>

Desde una mentalidad web moderna, este código puede parecer repetitivo. Pero en email marketing responde a una necesidad concreta: conseguir que el diseño sea estable en el mayor número posible de clientes.

Checklist de CSS compatible para email marketing

Antes de enviar una campaña, conviene revisar una pequeña lista de comprobación. No sustituye a las pruebas reales, pero ayuda a detectar problemas comunes.

Estructura

  • ¿El layout principal está basado en tablas?
  • ¿El ancho máximo está controlado?
  • ¿El email se lee bien aunque no carguen las imágenes?
  • ¿Las columnas se comportan correctamente en móvil?

Estilos

  • ¿Los estilos esenciales están inline?
  • ¿La tipografía tiene fuentes fallback?
  • ¿Los colores de texto y fondo están definidos de forma explícita?
  • ¿Los botones tienen texto real?
  • ¿El espaciado depende más de padding que de margin?

Compatibilidad

  • ¿El diseño funciona sin sombras?
  • ¿El diseño funciona sin bordes redondeados?
  • ¿El contenido se entiende sin fuentes externas?
  • ¿Las media queries mejoran el diseño, pero no lo sostienen por completo?
  • ¿Se ha probado en Gmail, Outlook, Apple Mail y móvil?

Accesibilidad y experiencia

  • ¿El texto tiene buen contraste?
  • ¿Los enlaces son claros?
  • ¿Los botones tienen un tamaño cómodo para móvil?
  • ¿Las imágenes tienen texto alternativo útil?
  • ¿El orden de lectura tiene sentido?

Esta parte también conecta con una idea muy importante en diseño web y experiencia de usuario: reducir la fricción. Un email con una jerarquía clara, botones visibles y contenido fácil de escanear reduce la carga cognitiva y ayuda al usuario a tomar decisiones con menos esfuerzo.

Preguntas frecuentes sobre CSS en email

¿Puedo usar CSS en emails HTML?

Sí, puedes usar CSS en emails HTML, pero con más restricciones que en una web. Lo más recomendable es utilizar CSS inline para los estilos esenciales, mantener una estructura simple y probar el resultado en distintos clientes de correo antes del envío.

¿Flexbox y Grid funcionan en email marketing?

Pueden funcionar en algunos clientes, pero no son la opción más segura para la estructura principal de una campaña. Para emails comerciales que deben verse correctamente en muchos entornos, sigue siendo más fiable trabajar con tablas HTML y CSS inline.

¿Por qué Outlook rompe tantos emails?

Porque algunas versiones de Outlook, especialmente las clásicas de escritorio para Windows, no interpretan el HTML y el CSS como un navegador moderno. Esto puede afectar a fondos, espaciados, imágenes, bordes, fuentes y otros estilos visuales.

Diseñar emails con CSS es aceptar las reglas del medio

El CSS en email marketing no va de demostrar cuánto CSS sabemos usar. Va de tomar buenas decisiones para que el mensaje llegue bien, se lea bien y funcione en contextos muy diferentes.

Una newsletter no necesita comportarse como una web moderna. Necesita cargar correctamente, mantener la identidad visual de la marca, guiar la lectura y facilitar una acción clara. Para conseguirlo, muchas veces el mejor camino es aceptar las limitaciones del medio: tablas para la estructura, CSS inline para lo esencial, estilos progresivos para clientes modernos y pruebas constantes antes de enviar.

La clave no está en renunciar al diseño, sino en diseñar con realismo. Puedes usar colores, tipografías cuidadas, jerarquía visual, botones atractivos, fondos sólidos, imágenes optimizadas y pequeños detalles decorativos. Pero conviene evitar que la experiencia dependa de propiedades frágiles como sombras, layouts modernos, animaciones o fuentes externas.

En definitiva, el CSS que realmente funciona en email marketing es el que entiende su contexto. No el más moderno, no el más elegante, no el más parecido al de una landing page. El que funciona es el CSS que sobrevive a Gmail, se adapta a móvil, no se rompe en Outlook y mantiene intacto lo más importante: el mensaje.

Cómo crear loaders animados solo con CSS

Cuando una página necesita unos segundos para cargar datos, procesar una acción o completar una petición, el usuario debe recibir alguna señal que confirme que el sistema continúa funcionando. De lo contrario, puede pensar que la interfaz se ha bloqueado, pulsar varias veces el mismo botón o abandonar el sitio antes de que termine el proceso.

Los loaders cumplen precisamente esa función. Son pequeños indicadores visuales que comunican que existe una tarea en curso y que es necesario esperar. Aunque pueden desarrollarse mediante imágenes, SVG o JavaScript, en muchos casos es posible crear un loader CSS animado utilizando únicamente HTML y CSS.

Un círculo giratorio, una secuencia de puntos, unas barras que cambian de tamaño o una línea de progreso indeterminada pueden construirse con unas pocas propiedades, pseudoelementos y una regla @keyframes.

En esta guía veremos cómo crear diferentes loaders animados solo con CSS, cómo personalizarlos y qué medidas debemos aplicar para que sean accesibles, eficientes y coherentes con el diseño de la interfaz.

Si todavía no tienes claros los fundamentos de las transiciones y las animaciones, puedes consultar primero esta guía básica de animaciones CSS, donde se explican conceptos como transition, animation y @keyframes desde cero.

Qué es un loader CSS y para qué sirve

Un loader es un componente visual que informa de que una aplicación está realizando una operación cuyo resultado todavía no está disponible.

Puede aparecer mientras:

  • se recuperan datos de una API;
  • se envía un formulario;
  • se procesa un pago;
  • se sube un archivo;
  • se carga una nueva página;
  • se generan resultados;
  • se prepara una imagen;
  • se actualiza una sección dinámica.

El loader no acelera técnicamente la operación. Su función consiste en hacer comprensible el tiempo de espera.

Cuando el usuario recibe una respuesta visual inmediata, entiende que su acción ha sido registrada. Aunque el proceso tarde exactamente lo mismo, la experiencia resulta más clara y controlada.

Diferencias entre loader, spinner y barra de progreso

Los términos loader y spinner se utilizan a menudo como si fueran sinónimos, pero no significan exactamente lo mismo.

Un loader es cualquier indicador de carga. Puede tener forma circular, lineal, geométrica o incluso representar una pequeña ilustración.

Un spinner CSS es un tipo específico de loader basado en un movimiento giratorio. El ejemplo más habitual es un círculo con una parte del borde destacada que gira de manera continua.

Por otra parte, una barra de progreso puede ser determinada o indeterminada:

  • Una barra determinada muestra cuánto falta para completar el proceso, normalmente mediante un porcentaje.
  • Una barra indeterminada comunica que existe una operación en curso, pero no representa su duración ni su progreso exacto.

Cuando no conocemos el avance real de una tarea, no debemos mostrar un porcentaje inventado. En esos casos, resulta más apropiado utilizar un spinner, una animación de puntos o una barra indeterminada.

Qué significa crear un loader solo con CSS

Crear un loader solo con CSS significa que su representación visual y su movimiento no necesitan imágenes externas, GIF animados ni lógica JavaScript.

La animación puede construirse mediante:

  • bordes;
  • fondos;
  • transformaciones;
  • cambios de opacidad;
  • pseudoelementos;
  • retrasos de animación;
  • reglas @keyframes.

No obstante, conviene aclarar una cuestión importante: CSS puede animar el loader, pero normalmente no decide cuándo debe aparecer o desaparecer.

En una aplicación real, JavaScript, React, Vue, Angular o la tecnología utilizada en el proyecto controlará el estado de carga. CSS se encargará de la apariencia, mientras que la lógica de la aplicación determinará cuándo mostrar u ocultar el componente.

Fundamentos de una animación de carga CSS

Antes de construir los ejemplos, resulta útil conocer las piezas básicas que intervienen en cualquier animación de carga.

La estructura HTML del loader

Un loader sencillo puede partir de un único elemento:

<div class="spinner"></div>

Esta estructura puede ser suficiente para representar la parte visual, pero no comunica qué está ocurriendo a los usuarios de tecnologías de asistencia.

Una versión más completa sería la siguiente:

<div class="loader" role="status">
  <span class="loader__spinner" aria-hidden="true"></span>
  <span class="loader__text">Cargando contenido…</span>
</div>

En este ejemplo:

  • role="status" identifica el contenido como una actualización de estado;
  • aria-hidden="true" oculta el elemento decorativo a los lectores de pantalla;
  • el texto explica qué operación se está realizando.

Esta separación entre contenido y decoración permite cambiar el aspecto del loader sin perder información esencial.

Cómo funciona @keyframes

La regla @keyframes define los distintos estados que atravesará un elemento durante una animación.

Un giro completo puede declararse así:

@keyframes spin {
  to {
    transform: rotate(1turn);
  }
}

El valor 1turn equivale a una vuelta completa. También podríamos utilizar 360deg, aunque 1turn expresa de forma muy clara la intención del movimiento.

Después debemos asociar la animación al elemento:

.loader__spinner {
  animation: spin 0.8s linear infinite;
}

La propiedad abreviada animation indica:

  • spin: nombre de la animación;
  • 0.8s: duración de cada ciclo;
  • linear: velocidad constante;
  • infinite: repetición indefinida.

Propiedades principales de animation

Aunque la sintaxis abreviada es cómoda, una animación está formada por diferentes propiedades:

.elemento {
  animation-name: spin;
  animation-duration: 0.8s;
  animation-timing-function: linear;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-direction: normal;
  animation-fill-mode: none;
}

Para un loading CSS no siempre necesitaremos configurarlas todas. Las más habituales serán el nombre, la duración, la curva de velocidad, el retraso y el número de repeticiones.

Cómo crear un spinner CSS circular

El círculo giratorio es probablemente el tipo de loader más reconocible. Puede construirse aplicando un borde uniforme a un elemento circular y cambiando el color de uno de sus lados.

HTML del spinner

<div class="loader" role="status">
  <span class="spinner" aria-hidden="true"></span>
  <span>Cargando contenido…</span>
</div>

CSS del spinner

.loader {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  font-family: sans-serif;
  color: #2d2433;
}

.spinner {
  width: 2.5rem;
  height: 2.5rem;
  border: 0.3rem solid #eadde7;
  border-top-color: #cc2b5e;
  border-radius: 50%;
  animation: spinner-rotation 0.8s linear infinite;
}

@keyframes spinner-rotation {
  to {
    transform: rotate(1turn);
  }
}

La clave está en border-top-color. Como uno de los lados tiene un color diferente, el giro produce la sensación de que una sección del círculo está avanzando continuamente.

La propiedad border-radius: 50% transforma el elemento cuadrado en un círculo, mientras que transform: rotate() genera el movimiento.

Este ejemplo demuestra que no necesitamos una imagen para crear una forma reconocible. Si te interesa profundizar en este enfoque, en el artículo sobre cómo dibujar formas básicas con CSS encontrarás más ejemplos de círculos, triángulos, óvalos y otras figuras construidas únicamente con estilos.

Personalizar el spinner mediante variables CSS

Cuando un loader se utiliza en diferentes secciones, es recomendable definir sus principales características mediante propiedades personalizadas.

.spinner {
  --loader-size: 2.5rem;
  --loader-width: 0.3rem;
  --loader-color: #cc2b5e;
  --loader-track: #eadde7;
  --loader-speed: 0.8s;

  width: var(--loader-size);
  height: var(--loader-size);
  border: var(--loader-width) solid var(--loader-track);
  border-top-color: var(--loader-color);
  border-radius: 50%;
  animation: spinner-rotation var(--loader-speed) linear infinite;
}

Ahora podemos crear variantes modificando únicamente las variables:

.spinner--small {
  --loader-size: 1.25rem;
  --loader-width: 0.2rem;
}

.spinner--large {
  --loader-size: 4rem;
  --loader-width: 0.45rem;
  --loader-speed: 1.1s;
}

.spinner--secondary {
  --loader-color: #753a88;
}

Este sistema facilita la reutilización del componente y reduce la duplicación de estilos.

También permite adaptar el spinner a distintos contextos, como un botón pequeño, una pantalla completa o una tarjeta que actualiza su contenido.

Cómo crear un loader de puntos animados

Los puntos secuenciales funcionan especialmente bien en botones, mensajes, chats o espacios reducidos donde un spinner circular podría ocupar demasiado espacio.

Estructura HTML

<div class="loader-dots" role="status">
  <span class="loader-dots__animation" aria-hidden="true">
    <span></span>
    <span></span>
    <span></span>
  </span>

  <span class="visually-hidden">Cargando resultados…</span>
</div>

La animación se marca como decorativa y el texto permanece disponible para los lectores de pantalla.

Estilos de los puntos

.loader-dots__animation {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.loader-dots__animation span {
  width: 0.65rem;
  height: 0.65rem;
  background-color: #cc2b5e;
  border-radius: 50%;
  animation: dot-pulse 1.2s ease-in-out infinite;
}

.loader-dots__animation span:nth-child(2) {
  animation-delay: 0.15s;
}

.loader-dots__animation span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes dot-pulse {
  0%,
  80%,
  100% {
    transform: scale(0.65);
    opacity: 0.4;
  }

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

Cada punto utiliza la misma animación, pero comienza en un momento diferente gracias a animation-delay.

Este pequeño desfase crea una secuencia continua sin necesidad de definir tres animaciones distintas.

Ocultar el texto solo visualmente

Cuando no queremos mostrar el mensaje junto al loader, podemos ocultarlo visualmente sin eliminarlo del árbol de accesibilidad:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

No deberíamos utilizar display: none para este texto, ya que dejaría de estar disponible para las tecnologías de asistencia.

Cómo crear un loader de barras con CSS

Otra alternativa consiste en animar varias barras verticales. Este patrón puede recordar a un ecualizador y resulta adecuado para interfaces relacionadas con audio, procesamiento o análisis de datos.

HTML del loader

<div class="loader-bars" role="status">
  <span class="loader-bars__animation" aria-hidden="true">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
  </span>

  <span class="visually-hidden">Procesando información…</span>
</div>

CSS de las barras animadas

.loader-bars__animation {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  height: 2rem;
}

.loader-bars__animation span {
  width: 0.35rem;
  height: 100%;
  background-color: #753a88;
  border-radius: 999px;
  transform-origin: center;
  animation: bar-scale 1s ease-in-out infinite;
}

.loader-bars__animation span:nth-child(2) {
  animation-delay: 0.1s;
}

.loader-bars__animation span:nth-child(3) {
  animation-delay: 0.2s;
}

.loader-bars__animation span:nth-child(4) {
  animation-delay: 0.3s;
}

@keyframes bar-scale {
  0%,
  100% {
    transform: scaleY(0.35);
    opacity: 0.5;
  }

  50% {
    transform: scaleY(1);
    opacity: 1;
  }
}

En lugar de modificar directamente la altura de las barras, utilizamos transform: scaleY().

Visualmente, el resultado es similar a cambiar height, pero mantenemos intacto el espacio que ocupa cada elemento dentro del documento.

Cómo crear una barra de carga indeterminada

Cuando el loader debe ocupar un espacio horizontal, podemos crear una línea que se desplace dentro de un contenedor.

Este patrón es apropiado cuando queremos comunicar actividad sin representar un porcentaje exacto.

Marcado HTML

<div class="loading-section" role="status">
  <div class="loading-bar" aria-hidden="true"></div>
  <p>Cargando los datos solicitados…</p>
</div>

Estilos CSS

.loading-section {
  width: min(100%, 24rem);
  font-family: sans-serif;
  color: #2d2433;
}

.loading-bar {
  position: relative;
  width: 100%;
  height: 0.5rem;
  overflow: hidden;
  background-color: #eadde7;
  border-radius: 999px;
}

.loading-bar::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 40%;
  background-color: #cc2b5e;
  border-radius: inherit;
  transform: translateX(-125%);
  animation: loading-progress 1.4s ease-in-out infinite;
}

@keyframes loading-progress {
  to {
    transform: translateX(350%);
  }
}

El contenedor oculta todo lo que sobresale mediante overflow: hidden. El pseudoelemento comienza fuera del extremo izquierdo y atraviesa la barra utilizando translateX().

Este loader comunica que una tarea está activa, pero no muestra un avance real. Por ese motivo, no debemos presentarlo como si fuera una barra de progreso determinada.

Crear un loader con pseudoelementos

Los pseudoelementos ::before y ::after permiten generar formas adicionales sin incorporar más etiquetas al HTML.

Podemos utilizarlos para crear un loader con dos anillos que giren en direcciones distintas:

<div class="double-spinner" role="status">
  <span class="visually-hidden">Cargando aplicación…</span>
</div>
.double-spinner {
  position: relative;
  width: 3.5rem;
  height: 3.5rem;
}

.double-spinner::before,
.double-spinner::after {
  content: "";
  position: absolute;
  border-radius: 50%;
}

.double-spinner::before {
  inset: 0;
  border: 0.3rem solid transparent;
  border-top-color: #cc2b5e;
  border-right-color: #cc2b5e;
  animation: outer-rotation 1s linear infinite;
}

.double-spinner::after {
  inset: 0.65rem;
  border: 0.25rem solid transparent;
  border-bottom-color: #753a88;
  animation: inner-rotation 0.75s linear infinite reverse;
}

@keyframes outer-rotation {
  to {
    transform: rotate(1turn);
  }
}

@keyframes inner-rotation {
  to {
    transform: rotate(1turn);
  }
}

Aunque el diseño utiliza dos formas, el HTML solo necesita un contenedor. Los pseudoelementos se reservan para la parte puramente decorativa.

Este mismo recurso puede emplearse para construir otros elementos de interfaz. En la guía sobre cómo dibujar iconos sencillos con CSS sin utilizar SVG ni imágenes puedes ver cómo combinar bordes, transformaciones y pseudoelementos para crear gráficos ligeros.

Accesibilidad en los loaders animados

Un loader no debería ser únicamente un elemento que se mueve. Si el estado de carga solo se comunica visualmente, algunas personas no sabrán que la interfaz está procesando información.

Añadir un mensaje de estado

La solución más clara consiste en acompañar el loader con un texto:

<div class="loader" role="status">
  <span class="spinner" aria-hidden="true"></span>
  <span>Cargando productos…</span>
</div>

El mensaje debería describir la acción cuando resulte útil:

  • “Cargando productos…”
  • “Enviando formulario…”
  • “Procesando el pago…”
  • “Preparando el archivo…”
  • “Actualizando resultados…”

Un mensaje específico suele resultar más informativo que un “Cargando…” genérico.

Utilizar role="status"

El rol status identifica una actualización que puede anunciarse sin desplazar el foco del usuario.

El elemento visual puede llevar aria-hidden="true" para evitar información innecesaria:

<span class="spinner" aria-hidden="true"></span>

No es necesario describir que existe un círculo de color girando. Lo importante es comunicar que la operación continúa.

Indicar que una región está ocupada

Cuando una sección concreta se está actualizando, puede utilizarse aria-busy="true" en el contenedor correspondiente:

<section class="results" aria-busy="true">
  <div class="loader" role="status">
    <span class="spinner" aria-hidden="true"></span>
    <span>Cargando resultados…</span>
  </div>
</section>

Cuando termina la operación, la aplicación debe actualizar el atributo:

<section class="results" aria-busy="false">
  <!-- Contenido cargado -->
</section>

Este cambio normalmente requerirá JavaScript o la lógica del framework utilizado.

La accesibilidad no debe añadirse al final como una corrección aislada. Conviene plantearla desde el momento en el que diseñamos el componente. En el artículo sobre componentes UI accesibles encontrarás más recomendaciones para crear interfaces comprensibles mediante HTML semántico, estados visibles y compatibilidad con distintas formas de interacción.

Respetar prefers-reduced-motion

Algunas personas configuran su dispositivo para reducir las animaciones. Por ese motivo, un loader CSS animado debería tener en cuenta la media query prefers-reduced-motion.

Podemos detener las animaciones y mantener el mensaje visible:

@media (prefers-reduced-motion: reduce) {
  .spinner,
  .loader-dots__animation span,
  .loader-bars__animation span,
  .loading-bar::before,
  .double-spinner::before,
  .double-spinner::after {
    animation: none;
  }
}

En este contexto, el indicador permanece estático mientras el texto continúa informando del proceso.

También podríamos sustituir el movimiento continuo por un cambio visual más discreto. Sin embargo, para un componente tan funcional como un loader, detener la animación suele ser una solución sencilla y comprensible.

Lo importante es que la información no dependa exclusivamente del movimiento. Aunque la animación se detenga, el usuario debe poder saber que existe una operación en curso.

Rendimiento de una animación de carga CSS

Los loaders suelen repetirse indefinidamente mientras dura una operación. Por tanto, una animación mal planteada puede consumir recursos durante todo el periodo de espera.

Siempre que sea posible, conviene priorizar:

  • transform;
  • opacity.

Estas propiedades permiten crear giros, desplazamientos, escalados y pulsaciones sin modificar directamente la geometría del documento.

Propiedades que conviene evitar

Animar propiedades como width, height, top, left, margin o padding puede obligar al navegador a recalcular posiciones y dimensiones durante la animación.

Por ejemplo, podríamos cambiar la altura de una barra de esta forma:

@keyframes inefficient-bar {
  from {
    height: 0.5rem;
  }

  to {
    height: 2rem;
  }
}

Sin embargo, podemos obtener un resultado visual parecido mediante una transformación:

@keyframes efficient-bar {
  from {
    transform: scaleY(0.25);
  }

  to {
    transform: scaleY(1);
  }
}

El segundo planteamiento mantiene intacto el espacio reservado por el elemento y evita que el resto del contenido tenga que adaptarse a una altura cambiante.

No abusar de will-change

La propiedad will-change puede avisar al navegador de que una característica va a modificarse:

.spinner {
  will-change: transform;
}

Sin embargo, no debería aplicarse automáticamente a todos los elementos animados.

Mantener demasiadas optimizaciones activas puede aumentar el consumo de recursos. En un spinner pequeño, el navegador suele gestionar correctamente una transformación sencilla sin necesidad de añadir will-change.

Retirar el loader cuando deja de ser necesario

Ocultar visualmente un loader no siempre significa que su animación se haya detenido. Si el elemento permanece activo, podría continuar ejecutándose aunque ya no resulte visible.

Lo recomendable es:

  • retirar el componente cuando termina la operación;
  • detener la animación si el loader queda fuera de uso;
  • evitar mantener indicadores de carga permanentes;
  • comprobar que no existen varios loaders ejecutándose sin necesidad.

Cómo elegir la duración y el movimiento adecuados

La velocidad influye directamente en la percepción del componente.

Un spinner excesivamente rápido puede parecer nervioso, mientras que uno demasiado lento puede transmitir que la aplicación está bloqueada.

Como punto de partida, podemos utilizar estos intervalos:

  • entre 0.7s y 1.2s para un spinner circular;
  • entre 1s y 1.5s para una secuencia de puntos;
  • entre 1.2s y 2s para una barra indeterminada.

No se trata de reglas absolutas. La duración debe ajustarse al tamaño del componente, al tipo de movimiento y al lenguaje visual de la interfaz.

Utilizar una curva de velocidad coherente

Para una rotación continua, linear suele ser una buena elección:

animation: spinner-rotation 0.8s linear infinite;

Como no existe un principio ni un final perceptible entre las vueltas, mantener una velocidad constante evita cambios bruscos.

Para puntos, escalados o barras que suben y bajan, una curva como ease-in-out crea una aceleración más gradual:

animation: dot-pulse 1.2s ease-in-out infinite;

No existe una curva universal para todos los loaders. Debemos elegirla en función del tipo de movimiento que queremos representar.

Cuándo mostrar un loader y cuándo evitarlo

No todas las acciones necesitan una animación de carga.

Si una respuesta es prácticamente inmediata, mostrar un loader durante una fracción de segundo puede provocar un destello innecesario. En estos casos, suele ser preferible esperar un pequeño intervalo antes de mostrarlo.

Este retraso no se controla necesariamente desde CSS, ya que depende de la lógica de la aplicación. La idea consiste en mostrar el indicador únicamente cuando la espera empieza a ser perceptible.

También debemos evitar loaders que permanezcan indefinidamente sin ofrecer ninguna salida. Cuando una operación puede fallar, la interfaz debería contemplar:

  • un límite de espera;
  • un mensaje de error;
  • una opción para volver a intentarlo;
  • una forma de cancelar el proceso, cuando sea posible.

El loader comunica actividad, pero no reemplaza la gestión de errores.

Cuándo CSS puede quedarse corto

Para un spinner, unos puntos o una barra indeterminada, CSS suele ser suficiente. Sin embargo, las animaciones más complejas pueden requerir control sobre secuencias, pausas, eventos o líneas temporales.

En esos casos, una biblioteca de animación puede ofrecer un control más preciso. Puedes consultar esta introducción a las animaciones web con GSAP para conocer una alternativa basada en JavaScript.

Esto no significa que debamos utilizar una biblioteca para cualquier loader. Para un indicador de carga sencillo, CSS suele ser la opción más directa y fácil de mantener.

Errores frecuentes al crear loaders con CSS

Utilizar demasiados elementos HTML

Algunos loaders incluyen numerosas etiquetas para dibujar formas que podrían resolverse mediante bordes o pseudoelementos.

No es obligatorio reducir siempre el HTML al mínimo, pero conviene diferenciar entre los elementos que aportan significado y los que son puramente decorativos.

Omitir el texto de carga

Una animación sin mensaje puede ser suficiente para algunos usuarios, pero no comunica el estado de forma universal.

Siempre que el contexto no sea completamente evidente, debemos proporcionar una etiqueta visible o accesible.

Mostrar un porcentaje falso

Una animación que avanza de cero a cien y vuelve a empezar no representa un progreso real.

Si desconocemos el porcentaje completado, debemos utilizar un indicador indeterminado y evitar transmitir una precisión que la aplicación no posee.

Crear una animación demasiado llamativa

El loader debe informar, no convertirse en el protagonista de la pantalla.

Los cambios bruscos de escala, los destellos rápidos o los giros excesivos pueden distraer y empeorar la experiencia.

Bloquear toda la interfaz sin necesidad

No todas las operaciones requieren una capa superpuesta que impida interactuar con la página.

Si solo se está actualizando una sección, podemos colocar el loader dentro de esa zona y mantener disponible el resto del contenido.

Olvidar los estados de error y contenido vacío

Una interfaz no debería quedar atrapada en un estado de carga eterno.

Además del loader, debemos diseñar qué ocurrirá cuando:

  • la petición falle;
  • no existan resultados;
  • se pierda la conexión;
  • la operación tarde más de lo esperado;
  • el usuario cancele el proceso.

Preguntas frecuentes sobre loaders animados con CSS

¿Se puede crear un loader completamente sin JavaScript?

Sí. La apariencia y el movimiento pueden crearse únicamente con HTML y CSS. Un spinner, unos puntos animados o una barra indeterminada no necesitan JavaScript para funcionar visualmente.

Sin embargo, en una aplicación dinámica suele ser necesario utilizar JavaScript o la lógica de un framework para mostrar el loader al comenzar una operación y retirarlo cuando termina.

¿Qué propiedades CSS son mejores para animar un loader?

Siempre que el diseño lo permita, conviene utilizar transform y opacity.

Con ellas podemos crear rotaciones, escalados, desplazamientos y pulsaciones sin modificar directamente el tamaño o la posición calculada de los elementos.

Propiedades como width, height, top o left deberían reservarse para casos en los que no exista una alternativa razonable.

¿Cómo puedo hacer que un spinner CSS sea accesible?

El spinner visual debería marcarse como decorativo mediante aria-hidden="true" y acompañarse de un mensaje que describa el estado, como “Cargando resultados…”.

También puede utilizarse role="status" para identificar la actualización y aria-busy para indicar que una región está siendo procesada. Además, conviene respetar prefers-reduced-motion para reducir o detener la animación cuando el usuario lo haya solicitado.

Una espera bien diseñada también comunica

Crear una animación de carga CSS puede parecer un detalle menor dentro de una interfaz, pero su impacto va más allá del movimiento. Un buen loader confirma que la aplicación ha recibido una acción, reduce la incertidumbre y ayuda al usuario a interpretar lo que está ocurriendo.

La parte visual puede resolverse con muy poco código. Un borde circular, una transformación y una regla @keyframes son suficientes para construir un spinner CSS funcional.

Sin embargo, el verdadero trabajo consiste en decidir cuándo mostrarlo, cómo comunicar el estado, qué ocurrirá si la operación falla y de qué manera se adaptará a las preferencias de movimiento.

Por eso, el mejor loader no es necesariamente el más original ni el más complejo. Es aquel que aparece cuando hace falta, mantiene una animación discreta, no perjudica el rendimiento y desaparece en cuanto el contenido está preparado.

En diseño de interfaces, esperar no siempre puede evitarse. Lo que sí podemos hacer es conseguir que esa espera resulte clara, accesible y coherente.