Dominando los Componentes Interactivos con Tailwind CSS y JavaScript Vainilla ✨
Este tutorial te guiará paso a paso en la creación de componentes UI interactivos y accesibles utilizando la potencia de Tailwind CSS para el estilo y JavaScript vanilla para la interactividad. Aprenderás a construir modales, acordeones y menús desplegables desde cero, mejorando tus habilidades en desarrollo web frontend.
¡Bienvenido a este tutorial sobre cómo potenciar tus interfaces con componentes interactivos usando Tailwind CSS y JavaScript vainilla! En el mundo del desarrollo web moderno, la interactividad es clave para una experiencia de usuario fluida y atractiva. Si bien Tailwind CSS es excepcional para construir interfaces rápidamente con sus clases de utilidad, por sí solo no añade interactividad.
Aquí es donde JavaScript entra en juego, permitiéndonos dar vida a nuestros diseños. En lugar de depender de pesadas librerías de JS, nos enfocaremos en JavaScript vainilla (plain JavaScript), lo que nos dará un mayor control, optimizará el rendimiento y reducirá la carga de dependencias en nuestros proyectos. Este enfoque es perfecto para proyectos donde necesitas control total o quieres mantener el tamaño del bundle al mínimo.
🎯 ¿Qué vamos a construir?
En este tutorial, crearemos tres componentes interactivos fundamentales que encontrarás en casi cualquier aplicación web:
- Modal/Popup: Un diálogo que se superpone al contenido principal, ideal para notificaciones, formularios o detalles de elementos.
- Acordeón: Una lista de elementos que pueden expandirse o contraerse para revelar u ocultar contenido, perfecto para FAQs o secciones de preguntas y respuestas.
- Menú Desplegable (Dropdown): Un menú que aparece al hacer clic o pasar el ratón sobre un elemento, comúnmente usado para navegación o acciones contextuales.
Para cada componente, nos centraremos en:
- Estructura HTML semántica: Para una buena base.
- Estilo con Tailwind CSS: Para un diseño limpio y consistente.
- Interactividad con JavaScript vanilla: Para la funcionalidad.
- Consideraciones de accesibilidad (ARIA): Para asegurar que nuestros componentes sean utilizables por todos.
🛠️ Configuración inicial del proyecto
Antes de sumergirnos en la creación de componentes, necesitamos una configuración básica de Tailwind CSS. Si ya tienes un proyecto configurado, puedes saltar esta sección. De lo contrario, sigue estos pasos.
- Crea un nuevo directorio para tu proyecto:
mkdir tailwind-interactivo
cd tailwind-interactivo
- Inicializa npm:
npm init -y
- Instala Tailwind CSS y sus dependencias:
npm install -D tailwindcss postcss autoprefixer
- Genera los archivos de configuración de Tailwind y PostCSS:
npx tailwindcss init -p
Esto creará `tailwind.config.js` y `postcss.config.js` en tu directorio raíz.
5. Configura tailwind.config.js para escanear tus archivos HTML y JS en busca de clases de Tailwind:
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./*.html',
'./src/**/*.js',
],
theme: {
extend: {},
},
plugins: [],
};
- Crea tu archivo CSS principal (
./src/input.css):
/* src/input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
- Crea tu archivo HTML (
./index.html):
<!-- index.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Componentes Interactivos con Tailwind y JS Vainilla</title>
<link rel="stylesheet" href="./dist/output.css">
</head>
<body class="font-sans bg-gray-100 p-8">
<h1 class="text-4xl font-bold text-gray-800 mb-8">Componentes Interactivos</h1>
<!-- Aquí irán nuestros componentes -->
<script src="./src/main.js"></script>
</body>
</html>
- Crea tu archivo JavaScript (
./src/main.js): Por ahora, estará vacío.
// src/main.js
console.log("¡JavaScript cargado!");
- Añade un script de build a tu
package.jsonpara compilar Tailwind CSS:
// package.json (añade esto dentro de "scripts")
"scripts": {
"build": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch"
},
- Ejecuta el script de build:
npm run build
Esto iniciará el proceso de compilación de Tailwind en modo `watch`, lo que significa que se recompilará automáticamente cada vez que guardes cambios en tus archivos CSS, HTML o JS.
Ahora estás listo para empezar a construir los componentes. Mantén la terminal con npm run build ejecutándose en segundo plano.
1. 🖼️ Creando un Modal o Pop-up
Un modal es una ventana superpuesta que requiere la interacción del usuario antes de que pueda volver a la aplicación principal. Es crucial para notificaciones importantes, formularios de login o confirmaciones.
Estructura HTML del Modal
Primero, definimos la estructura del modal en index.html. Usaremos clases de Tailwind para el estilo y atributos ARIA para la accesibilidad.
<!-- index.html (dentro del body, después del h1) -->
<div class="mb-8">
<button id="openModalBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded shadow">Abrir Modal</button>
</div>
<div id="myModal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center p-4 z-50 hidden" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<div class="bg-white rounded-lg shadow-xl max-w-sm w-full p-6 relative">
<h2 id="modalTitle" class="text-2xl font-bold mb-4 text-gray-900">¡Bienvenido al Modal!</h2>
<p class="text-gray-700 mb-6">Este es un ejemplo de un modal simple construido con Tailwind CSS y JavaScript vanilla. Puedes cerrarlo haciendo clic en el botón o fuera del modal.</p>
<div class="flex justify-end">
<button id="closeModalBtn" class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">Cerrar</button>
</div>
<button class="absolute top-3 right-3 text-gray-500 hover:text-gray-700 text-xl" aria-label="Cerrar modal">×</button>
</div>
</div>
📌 Notas sobre el HTML:
fixed inset-0: Para que el fondo oscuro ocupe toda la pantalla.bg-opacity-75: Para darle transparencia al fondo.hidden: Clase inicial para ocultar el modal. JavaScript la removerá y añadirá.role="dialog"yaria-modal="true": Esenciales para la accesibilidad, indican que es un diálogo modal.aria-labelledby="modalTitle": Vincula el título del modal para lectores de pantalla.z-50: Asegura que el modal esté por encima de otros elementos.
Lógica JavaScript para el Modal
Ahora, añadimos la interactividad en src/main.js.
// src/main.js
// --- Lógica para el Modal ---
const openModalBtn = document.getElementById('openModalBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const myModal = document.getElementById('myModal');
const modalCloseIcon = myModal.querySelector('.absolute.top-3.right-3'); // Selector para la 'x'
function openModal() {
myModal.classList.remove('hidden');
myModal.setAttribute('aria-hidden', 'false');
document.body.classList.add('overflow-hidden'); // Previene el scroll del body
}
function closeModal() {
myModal.classList.add('hidden');
myModal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('overflow-hidden'); // Restaura el scroll del body
}
// Abrir modal
openModalBtn.addEventListener('click', openModal);
// Cerrar modal con el botón 'Cerrar'
closeModalBtn.addEventListener('click', closeModal);
// Cerrar modal con el icono 'x'
modalCloseIcon.addEventListener('click', closeModal);
// Cerrar modal al hacer clic fuera del contenido del modal
myModal.addEventListener('click', (event) => {
if (event.target === myModal) {
closeModal();
}
});
// Cerrar modal con la tecla Escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && !myModal.classList.contains('hidden')) {
closeModal();
}
});
2. 📚 Implementando un Acordeón
Los acordeones son excelentes para organizar grandes cantidades de contenido en secciones colapsables, mejorando la legibilidad y la navegación. Son muy comunes en secciones de preguntas frecuentes (FAQ).
Estructura HTML del Acordeón
Aquí, cada ítem del acordeón tendrá un encabezado (botón) y un panel de contenido.
<!-- index.html (después del modal) -->
<h2 class="text-3xl font-bold text-gray-800 mt-12 mb-6">Preguntas Frecuentes</h2>
<div id="accordion" class="max-w-xl mx-auto bg-white shadow-md rounded-lg overflow-hidden">
<!-- Item de Acordeón 1 -->
<div class="border-b border-gray-200 last:border-b-0">
<h3 class="text-lg font-semibold">
<button class="accordion-header flex justify-between items-center w-full py-4 px-6 text-left text-gray-800 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" aria-expanded="false" aria-controls="panel1" id="header1">
¿Qué es Tailwind CSS?
</button>
</h3>
<div id="panel1" class="accordion-panel hidden px-6 pb-4 text-gray-700" role="region" aria-labelledby="header1">
<p>Tailwind CSS es un framework CSS de primera utilidad para construir rápidamente interfaces de usuario personalizadas.</p>
<p class="mt-2">Proporciona un conjunto de clases de bajo nivel que puedes componer directamente en tu marcado.</p>
</div>
</div>
<!-- Item de Acordeón 2 -->
<div class="border-b border-gray-200 last:border-b-0">
<h3 class="text-lg font-semibold">
<button class="accordion-header flex justify-between items-center w-full py-4 px-6 text-left text-gray-800 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" aria-expanded="false" aria-controls="panel2" id="header2">
¿Por qué usar JavaScript vanilla?
</button>
</h3>
<div id="panel2" class="accordion-panel hidden px-6 pb-4 text-gray-700" role="region" aria-labelledby="header2">
<p>Usar JavaScript vanilla reduce la dependencia de librerías externas, mejora el rendimiento y te da un control más fino sobre la lógica de tus componentes.</p>
<p class="mt-2">Es ideal para añadir interactividad a elementos específicos sin la sobrecarga de un framework JS completo.</p>
</div>
</div>
<!-- Item de Acordeón 3 -->
<div class="border-b border-gray-200 last:border-b-0">
<h3 class="text-lg font-semibold">
<button class="accordion-header flex justify-between items-center w-full py-4 px-6 text-left text-gray-800 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" aria-expanded="false" aria-controls="panel3" id="header3">
¿Es accesible un acordeón?
</button>
</h3>
<div id="panel3" class="accordion-panel hidden px-6 pb-4 text-gray-700" role="region" aria-labelledby="header3">
<p>Sí, si se implementa correctamente. Es fundamental usar los atributos ARIA `aria-expanded` y `aria-controls` para que los lectores de pantalla puedan entender el estado y la relación entre el botón y el panel.</p>
<p class="mt-2">También es importante manejar la navegación con teclado.</p>
</div>
</div>
```
📌 Notas sobre el HTML:
accordion-header: Clase para seleccionar los botones que expanden/contraen.accordion-panel: Clase para seleccionar los paneles de contenido.aria-expanded="false": Indica que el panel está inicialmente cerrado.aria-controls: Vincula el botón al ID del panel que controla.role="region" aria-labelledby: Para la accesibilidad del panel.- El SVG de la flecha rotará con CSS mediante JavaScript.
Lógica JavaScript para el Acordeón
Vamos a añadir el código en src/main.js para gestionar la funcionalidad del acordeón.
// src/main.js (después de la lógica del modal)
// --- Lógica para el Acordeón ---
const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', () => {
const panel = header.nextElementSibling; // El panel es el siguiente hermano del header
const isExpanded = header.getAttribute('aria-expanded') === 'true';
const arrowIcon = header.querySelector('svg');
// Cierra todos los demás paneles abiertos (comportamiento de acordeón 'single-open')
accordionHeaders.forEach(otherHeader => {
if (otherHeader !== header) {
const otherPanel = otherHeader.nextElementSibling;
const otherArrowIcon = otherHeader.querySelector('svg');
if (otherHeader.getAttribute('aria-expanded') === 'true') {
otherHeader.setAttribute('aria-expanded', 'false');
otherPanel.classList.add('hidden');
otherArrowIcon.classList.remove('rotate-180');
}
}
});
// Alterna el panel actual
if (isExpanded) {
header.setAttribute('aria-expanded', 'false');
panel.classList.add('hidden');
arrowIcon.classList.remove('rotate-180');
} else {
header.setAttribute('aria-expanded', 'true');
panel.classList.remove('hidden');
arrowIcon.classList.add('rotate-180');
}
});
});
3. 🔽 Creando un Menú Desplegable (Dropdown)
Los menús desplegables son un componente UI común para la navegación o para ofrecer una lista de opciones al usuario de forma compacta.
Estructura HTML del Menú Desplegable
Crearemos un botón que al hacer clic mostrará una lista de enlaces.
<!-- index.html (después del acordeón) -->
<h2 class="text-3xl font-bold text-gray-800 mt-12 mb-6">Navegación Desplegable</h2>
<div class="relative inline-block text-left">
<div>
<button id="dropdownButton" type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500" aria-expanded="false" aria-haspopup="true">
Opciones
</button>
</div>
<div id="dropdownMenu" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none hidden" role="menu" aria-orientation="vertical" aria-labelledby="dropdownButton" tabindex="-1">
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem" tabindex="-1" id="menu-item-0">Perfil</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem" tabindex="-1" id="menu-item-1">Configuración</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem" tabindex="-1" id="menu-item-2">Soporte</a>
<form method="POST" action="#" role="none">
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem" tabindex="-1" id="menu-item-3">Cerrar sesión</button>
</form>
</div>
</div>
```
📌 Notas sobre el HTML:
relative: El contenedor padre debe ser relativo para que el menúabsolutese posicione correctamente.aria-expanded="false"yaria-haspopup="true": Informan a los lectores de pantalla que el botón controla un pop-up y si está expandido.role="menu",aria-orientation="vertical",aria-labelledby: Esenciales para la accesibilidad del menú.tabindex="-1": Hace que el menú y sus ítems sean enfocables programáticamente, pero no por tabulación directa, lo que es común para elementos que aparecen y desaparecen. El foco se gestiona con JS.
Lógica JavaScript para el Menú Desplegable
La interactividad incluye abrir/cerrar y cerrar al hacer clic fuera o con Escape.
// src/main.js (después de la lógica del acordeón)
// --- Lógica para el Dropdown ---
const dropdownButton = document.getElementById('dropdownButton');
const dropdownMenu = document.getElementById('dropdownMenu');
function toggleDropdown() {
const isExpanded = dropdownButton.getAttribute('aria-expanded') === 'true';
dropdownButton.setAttribute('aria-expanded', String(!isExpanded));
dropdownMenu.classList.toggle('hidden');
if (!isExpanded) {
dropdownMenu.focus(); // Enfoca el menú cuando se abre
}
}
dropdownButton.addEventListener('click', toggleDropdown);
// Cerrar dropdown al hacer clic fuera
document.addEventListener('click', (event) => {
if (!dropdownButton.contains(event.target) && !dropdownMenu.contains(event.target)) {
if (!dropdownMenu.classList.contains('hidden')) {
toggleDropdown(); // Cierra el menú si está abierto y el clic fue fuera
}
}
});
// Cerrar dropdown con la tecla Escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && !dropdownMenu.classList.contains('hidden')) {
toggleDropdown();
dropdownButton.focus(); // Regresa el foco al botón
}
});
// Manejo de la navegación con flechas (opcional, pero mejora UX/accesibilidad)
dropdownMenu.addEventListener('keydown', (event) => {
const focusableItems = Array.from(dropdownMenu.querySelectorAll('a, button'));
const currentFocusedIndex = focusableItems.indexOf(document.activeElement);
if (event.key === 'ArrowDown') {
event.preventDefault();
const nextIndex = (currentFocusedIndex + 1) % focusableItems.length;
focusableItems[nextIndex].focus();
} else if (event.key === 'ArrowUp') {
event.preventDefault();
const prevIndex = (currentFocusedIndex - 1 + focusableItems.length) % focusableItems.length;
focusableItems[prevIndex].focus();
}
});
console.log("Componentes interactivos cargados.");
✨ Mejorando la Experiencia del Usuario (UX)
Además de la funcionalidad básica, podemos pulir nuestros componentes con pequeñas mejoras para una mejor UX.
Transiciones y Animaciones (Tailwind CSS)
Tailwind hace que añadir transiciones sea muy sencillo. Añade estas clases a los elementos que quieres animar:
- Modal (fondo):
transition-opacity duration-300 ease-outaldivdel modal. Para que la transición funcione bien, necesitarías manejar la opacidad con JS en lugar dehiddenyblock. Un ejemplo para el modal sería:
/* En tu CSS de entrada si quieres control más fino, o dinámicamente con JS */
.modal-enter { opacity: 0; }
.modal-enter-active { transition: opacity 0.3s ease-out; }
.modal-enter-to { opacity: 1; }
.modal-leave { opacity: 1; }
.modal-leave-active { transition: opacity 0.3s ease-in; }
.modal-leave-to { opacity: 0; }
Para simplificar con Tailwind y `hidden`, podemos transicionar solo el contenido, o usar clases `opacity-0` y `opacity-100` con `transition` junto a `pointer-events-none` o `visibility`.
**Opción más simple para el modal con `hidden`:** La clase `hidden` no permite transiciones directas de `opacity`. Para transiciones, es mejor controlar `opacity` y `visibility` o `pointer-events` con JS. Una forma es:
// Dentro de openModal()
myModal.classList.remove('hidden');
setTimeout(() => { myModal.classList.add('opacity-100'); }, 10); // pequeña espera
// Dentro de closeModal()
myModal.classList.remove('opacity-100');
myModal.classList.add('opacity-0'); // Asegura que se desvanece
setTimeout(() => { myModal.classList.add('hidden'); }, 300); // Duración de la transición
Y en el HTML del modal, en lugar de `bg-opacity-75`, usa `opacity-0 transition-opacity duration-300 ease-out` para el `div` principal del modal (el que tiene `fixed inset-0`).
-
Acordeón (flecha): Ya lo tenemos con
transition-transform duration-300 transform rotate-0yrotate-180en el SVG. -
Dropdown (menú): Puedes usar
transition ease-out duration-100y luego las clasestransform opacity-0 scale-95(para el estado inicial) ytransform opacity-100 scale-100(para el estado final). Al igual que el modal,hiddeninterfiere con esto. Podrías usar clases paraopacityytransformy cambiarpointer-events-none/pointer-events-auto.
<!-- En el div de dropdownMenu, cambia hidden por: -->
<div id="dropdownMenu" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none transition ease-out duration-100 transform opacity-0 scale-95 pointer-events-none" role="menu" aria-orientation="vertical" aria-labelledby="dropdownButton" tabindex="-1">
Y en JS:
function toggleDropdown() {
const isExpanded = dropdownButton.getAttribute('aria-expanded') === 'true';
dropdownButton.setAttribute('aria-expanded', String(!isExpanded));
if (!isExpanded) {
dropdownMenu.classList.remove('hidden', 'opacity-0', 'scale-95', 'pointer-events-none');
dropdownMenu.classList.add('opacity-100', 'scale-100', 'pointer-events-auto');
dropdownMenu.focus();
} else {
dropdownMenu.classList.remove('opacity-100', 'scale-100', 'pointer-events-auto');
dropdownMenu.classList.add('opacity-0', 'scale-95');
// Esperar a que la transición termine antes de añadir hidden
setTimeout(() => {
dropdownMenu.classList.add('hidden');
}, 100); // 100ms porque es la duration-100
}
}
Mejora de Accesibilidad con Tabindex y Foco
Ya hemos tocado la accesibilidad con ARIA, pero el manejo del foco es igualmente vital. Asegúrate de:
- Cuando un modal se abre, el foco debe moverse al primer elemento enfocable dentro del modal (o al propio modal si no hay elementos enfocables o es preferible así).
- Cuando un modal o dropdown se cierra, el foco debe volver al elemento que lo activó.
- Permitir la navegación con Tab y Shift + Tab dentro de los componentes para que los usuarios de teclado puedan interactuar con ellos.
Para el modal: El enfoque inicial en openModal podría ser en closeModalBtn o el primer enlace/input dentro del modal.
// En openModal()
myModal.classList.remove('hidden');
myModal.setAttribute('aria-hidden', 'false');
document.body.classList.add('overflow-hidden');
closeModalBtn.focus(); // O el primer elemento interactivo dentro del modal
Para el dropdown: Ya hemos añadido dropdownMenu.focus() al abrir y dropdownButton.focus() al cerrar con Escape. La navegación con flechas también mejora mucho la usabilidad para teclado.
✅ Conclusión y Próximos Pasos
¡Felicidades! Has construido tres componentes UI interactivos y accesibles utilizando la potente combinación de Tailwind CSS para el estilo y JavaScript vanilla para la interactividad. Este enfoque te proporciona flexibilidad, rendimiento y control total sobre tus componentes, sin la necesidad de dependencias externas pesadas.
Hemos cubierto la estructura HTML, el estilo con Tailwind, la lógica JavaScript y, crucialmente, las consideraciones de accesibilidad. Recuerda que la accesibilidad no es una opción, sino un requisito para construir experiencias web inclusivas.
¿Qué puedes hacer a continuación?
- Explora más componentes: Intenta construir tooltips, pestañas, carruseles, o barras de notificación.
- Optimiza el JavaScript: Considera refactorizar tu JS en módulos o clases para una mejor organización si tu proyecto crece.
- Añade animaciones complejas: Experimenta con las utilidades de animación y transición de Tailwind CSS para efectos más sofisticados.
- Integra con un bundler: Para proyectos más grandes, herramientas como Webpack o Vite pueden mejorar el proceso de desarrollo y optimizar el código final.
- Valida formularios: Si usas modales para formularios, integra la validación del lado del cliente para mejorar la UX.
Con estos conocimientos, estás bien equipado para crear interfaces dinámicas y receptivas que deleiten a tus usuarios. ¡Sigue experimentando y construyendo!
Tutoriales relacionados
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!