Un botón de retroalimentación con pulgar hacia arriba animado sin ninguna biblioteca de animaciones
Cree un widget de retroalimentación con pulgares arriba y abajo que incluya una animación de aparecimiento mediante keyframes, estados activos y una llamada a fetch, utilizando únicamente HTML, CSS y unas pocas líneas de JavaScript.
Fuentes: Creating an Interactive Thumbs Up Feedback Button with HTML, CSS and JavaScript
Los botones de pulgar arriba y pulgar abajo son una de las formas más económicas de preguntar a los usuarios si una página, respuesta o función les fue útil. La implementación habitual solo cambia el color de fondo al hacer clic, lo cual es fácil de pasar por alto cuando nada más en la página cambia. Esta guía muestra una versión ligeramente mejorada: al hacer clic en pulgar arriba, el ícono se sustituye brevemente por un smiley brillante que aparece con una animación de escala y rotación, para luego quedar como un pulgar arriba resaltado. Verá el marcado HTML, el CSS, el JavaScript que controla los estados y los aspectos que deben corregirse antes de lanzarlo.
Qué hace el widget
La interfaz cuenta con dos botones circulares, uno para retroalimentación positiva y otro para negativa. Al hacer clic en el botón de retroalimentación positiva, se ejecuta una secuencia breve:
- El ícono de pulgar arriba se reemplaza por un smiley rodeado de caracteres brillantes.
Las microinteracciones como esta hacen que una interfaz parezca receptiva sin necesidad de un framework de animaciones.
Marcado para los dos botones
Cada botón cuenta con un id para la programación, una clase compartida para el estilo y una clase modificadora para su propio color activo. Los íconos son caracteres emoji dentro de los botones:
<div class="feedback">
<button
id="thumbsUp"
class="feedback-btn thumbs-up"
aria-label="Thumbs up">
👍🏻
</button>
<button
id="thumbsDown"
class="feedback-btn thumbs-down"
aria-label="Thumbs down">
👎🏻
</button>
</div>
El aria-label es importante aquí. Sin él, un lector de pantalla anunciaría el nombre propio del emoji (o nada útil), por lo que la etiqueta proporciona a cada botón un nombre accesible y claro. Si desea que la tecnología de asistencia también sepa qué opción está seleccionada, agregue aria-pressed y actualícelo junto con la clase active.
Botones redondos con respuesta al pasar el cursor
El estilo base ajusta el tamaño, elimina el aspecto predeterminado, centra el ícono con flexbox y convierte al botón en un círculo:
.feedback-btn {
width: 52px;
height: 52px;
border: 1px solid #ddd;
background: #fff;
border-radius: 50%;
cursor: pointer;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.25s ease;
}
border-radius: 50% en un elemento cuadrado crea el círculo, y transition suaviza los cambios posteriores; especificar propiedades concretas en lugar de all evita animar elementos que no se pretendía.
Una regla para el paso del cursor aclara el fondo y agranda ligeramente el botón, de modo que el puntero recibe una respuesta inmediata:
.feedback-btn:hover {
background: #f5f5f5;
transform: scale(1.08);
}
Los keyframes detrás del efecto especial
El momento interesante es el cambio de un pulgar arriba a una carita sonriente brillante. Esto se gestiona mediante una regla @keyframes llamada sparkle:
@keyframes sparkle {
0% {
transform: scale(0.4) rotate(-20deg);
opacity: 0;
}
35% {
transform: scale(1.4) rotate(10deg);
opacity: 1;
}
65% {
transform: scale(1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
Como línea de tiempo: al 0% el contenido se reduce al 40%, se inclina 20 grados hacia atrás y queda invisible. Al 35% supera el tamaño normal al llegar al 140%, con una ligera inclinación en la dirección opuesta y total opacidad. Al 65% vuelve al tamaño normal con una pequeña rotación contraria, y al 100% mantiene su tamaño y ángulo naturales. Este exceso seguido de un ajuste es lo que se percibe como un “pop”. Tenga en cuenta que esta regla solo define el movimiento; una clase .sparkle separada que lo aplica con animation: sparkle 0.8s ease forwards aparece en el ejemplo completo al final.
Secuenciar los estados en JavaScript
El manejador de clics para el ícono de aprobación coordina el cambio de contenido, la clase de animación y el estado final:
thumbsUp.addEventListener("click", function () {
if (thumbsUp.classList.contains("active")) {
return;
}
thumbsUp.innerHTML = "✨😊✨";
thumbsUp.classList.add("sparkle");
setTimeout(function () {
thumbsUp.innerHTML = "👍";
thumbsUp.classList.remove("sparkle");
thumbsUp.classList.add("active");
}, 900);
});
Comienza con una verificación: si el botón ya tiene la clase active, el manejador devuelve, de modo que la animación no puede reproducirse al hacer clic repetidamente. Luego reemplaza el contenido por la carita sonriente:
thumbsUp.innerHTML = "✨😊✨";
y agrega la clase que activa los keyframes:
thumbsUp.classList.add("sparkle");
Se utiliza setTimeout de 900 milisegundos, un poco más tiempo que la animación de 0.8 segundos; posteriormente se restablece el carácter de pulgar hacia arriba, se elimina sparkle y se agrega active:
thumbsUp.innerHTML = "👍";
El contenido nunca proviene de la entrada del usuario, por lo que innerHTML es seguro en este caso, aunque textContent expresa mejor la intención.
Resaltar la opción seleccionada
La clase active proporciona la confirmación visual. La retroalimentación positiva muestra un fondo verde pálido y un borde verde:
.thumbs-up.active {
background: #e8f5e9;
border-color: #4caf50;
}
La retroalimentación negativa utiliza el mismo patrón en rojo:
.thumbs-down.active {
background: #ffebee;
border-color: #f44336;
}
Los colores distintos permiten ver de inmediato qué opción se registró, pero no hay que confiarse únicamente en el color; aria-pressed ayuda a los usuarios que no pueden distinguirlos.
Mantener las opciones mutuamente excluyentes
El pulgar hacia abajo utiliza una interacción más simple sin animaciones. Activa su propia clase active, elimina el estado positivo y, si ahora está seleccionado, restablece el ícono del pulgar hacia arriba a su forma original:
thumbsDown.addEventListener("click", function () {
thumbsDown.classList.toggle("active");
thumbsUp.classList.remove("active");
if (thumbsDown.classList.contains("active")) {
thumbsUp.innerHTML = "👍🏻";
}
});
Al desactivar el otro botón se evita que ambas opciones parezcan seleccionadas al mismo tiempo. Queda un caso límite: si el usuario hace clic en “me gusta” y luego en “no me gusta” dentro del plazo de 900 ms, el temporizador pendiente sigue activándose y vuelve a habilitar “me gusta”. Es necesario almacenar el identificador del temporizador y llamar a clearTimeout en el manejador de “no me gusta” para cerrar esa brecha. Tenga también en cuenta que un estado de “me gusta” activo no puede anularse haciendo clic en él, mientras que “no me gusta” permite cambiar de estado; haga que ambos funcionen de manera consistente.
Por qué vale la pena invertir esfuerzo en una pequeña animación
Un cambio de color estático indica que ocurrió algo; una animación breve (clic, movimiento, confirmación) hace que la interfaz parezca haber respondido. Esto es especialmente importante para las retroalimentaciones en línea, donde nada más en la página señala el éxito. Es adecuado para formularios de retroalimentación en cursos, calificaciones en Preguntas Frecuentes y bases de conocimientos, herramientas de soporte, reseñas de productos, encuestas, sitios de documentación, respuestas de chatbots de IA y herramientas internas.
Respete la opción prefers-reduced-motion desactivando los fotogramas clave para aquellos usuarios que solicitan menos animaciones.
Envío de la calificación al backend
Tal como está implementado, el widget es puramente visual. En un producto real, la elección debe guardarse, y el flujo incluye una solicitud:
User clicks 👍
↓
Animation plays
↓
Feedback recorded
↓
Database updated
↓
User sees confirmation
Cualquier backend funciona, ya sea PHP, Node.js, Python o otro. Desde el manejador de clics se enviará la calificación a un endpoint:
fetch("/api/feedback", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
feedback: "positive"
})
});
El servidor puede almacenar la página, el usuario, la respuesta, la marca de tiempo y el tipo de retroalimentación. Verifique response.ok, ya que fetch solo rechaza las solicitudes en caso de errores de red, y decida si la animación se ejecuta de forma optimista (más rápida, pero requiere un retroceso si la guardado falla) o solo después de que tenga éxito.
La página completa
Aquí está todo en un único archivo, incluida la clase .sparkle, una escala :active para el estado presionado, y el manejador de “me gusta” que elimina la selección de “no me gusta”:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feedback Buttons</title>
<style>
.feedback {
display: flex;
gap: 12px;
align-items: center;
}
.feedback-btn {
width: 52px;
height: 52px;
border: 1px solid #ddd;
background: #fff;
border-radius: 50%;
cursor: pointer;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.25s ease;
}
.feedback-btn:hover {
background: #f5f5f5;
transform: scale(1.08);
}
.feedback-btn:active {
transform: scale(0.92);
}
/* Sparkle animation */
.sparkle {
animation: sparkle 0.8s ease forwards;
}
@keyframes sparkle {
0% {
transform: scale(0.4) rotate(-20deg);
opacity: 0;
}
35% {
transform: scale(1.4) rotate(10deg);
opacity: 1;
}
65% {
transform: scale(1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
/* Filled thumbs up */
.thumbs-up.active {
background: #e8f5e9;
border-color: #4caf50;
}
.thumbs-down.active {
background: #ffebee;
border-color: #f44336;
}
</style>
</head>
<body>
<div class="feedback">
<button
id="thumbsUp"
class="feedback-btn thumbs-up"
aria-label="Thumbs up">
👍🏻
</button>
<button
id="thumbsDown"
class="feedback-btn thumbs-down"
aria-label="Thumbs down">
👎🏻
</button>
</div>
<script>
const thumbsUp = document.getElementById("thumbsUp");
const thumbsDown = document.getElementById("thumbsDown");
thumbsUp.addEventListener("click", function () {
// Prevent repeated animation
if (thumbsUp.classList.contains("active")) {
return;
}
// Step 1: Sparkling smiley
thumbsUp.innerHTML = "✨😊✨";
thumbsUp.classList.add("sparkle");
// Step 2: Change to filled thumbs up
setTimeout(function () {
thumbsUp.innerHTML = "👍";
thumbsUp.classList.remove("sparkle");
thumbsUp.classList.add("active");
}, 900);
// Remove thumbs down selection
thumbsDown.classList.remove("active");
});
thumbsDown.addEventListener("click", function () {
thumbsDown.classList.toggle("active");
// Remove thumbs up selection
thumbsUp.classList.remove("active");
if (thumbsDown.classList.contains("active")) {
thumbsUp.innerHTML = "👍🏻";
}
});
</script>
</body>
</html>
Conclusión
Un sistema de control de retroalimentación bien diseñado solo necesita HTML, CSS y unas pocas líneas de JavaScript. Los keyframes proporcionan el efecto de “pop”, las secuencias programadas de cambio de clases ordenan los estados, y el ícono resaltado final confirma la elección.
- Mantenga la lógica de animación en CSS y deje que JavaScript solo active o desactive clases y contenido.
aria-pressed y una opción de reducción de movimiento para que el widget siga siendo accesible.El mismo enfoque se aplica a íconos SVG, animaciones Lottie, indicadores sonoros y flujos de trabajo más avanzados basados en API.
Lecturas relacionadas
- ¿Quién debería controlar el comportamiento de la UI? Elegir entre HTML, CSS y JavaScript — Un marco para decidir cuándo las funciones nativas de HTML y CSS como dialog, popover, :has() y container queries deberían reemplazar a JavaScript, y cuándo no.
- Estructura, estilo y comportamiento: cómo HTML, CSS y JavaScript dividen el trabajo — Un mapa fácil de entender para principiantes que muestra qué funciones tienen HTML, CSS y JavaScript en una página web, cómo colaboran entre sí y cuáles son los errores más comunes que difuminan las fronteras entre ellos.