W3docs

Elementos Interactivos y Widgets en el Desarrollo Web

Aprende a crear widgets accesibles en JavaScript — sliders, modales, pestañas y acordeones — además de la Canvas API para gráficos dinámicos, con ejemplos.

Crear widgets personalizados y aprovechar las API de HTML puede mejorar significativamente la interactividad y la experiencia de usuario de tus aplicaciones web. Esta guía ofrece instrucciones paso a paso para construir elementos interactivos personalizados como sliders, modales y pestañas, y te presenta las API de HTML5 como la Canvas API para crear gráficos dinámicos.

Introducción

Un widget es un componente de UI interactivo y autónomo — un slider, un diálogo modal, un panel con pestañas, un acordeón — que el usuario puede manipular para cambiar lo que ve o para introducir datos. Los navegadores incluyen algunos widgets de forma nativa (<input type="range">, <dialog>, <details>), pero a menudo necesitarás crear los tuyos propios cuando requieras un comportamiento, estilo o diseño personalizado que los elementos integrados no proporcionan.

Esta guía muestra cómo construir los tres widgets a los que recurrirás con más frecuencia — un slider, un modal y un conjunto de pestañas — y luego presenta la Canvas API, una característica de HTML5 para dibujar gráficos dinámicos. Cada ejemplo conecta el comportamiento con el DOM y el manejo de eventos, por lo que es útil estar familiarizado con la selección de elementos y los eventos del navegador primero.

¿Widget nativo o widget personalizado?

Recurre a un elemento nativo antes de escribir JavaScript. Los widgets nativos son accesibles, operables con teclado y compatibles con formularios de forma predeterminada, y siguen funcionando aunque tu script no se cargue.

NecesidadElemento nativoCrear uno personalizado cuando…
Seleccionar un valor en un rango<input type="range">necesitas un control de dos pulgares o no lineal
Mostrar un diálogo bloqueante<dialog> + showModal()necesitas un diseño o animación totalmente personalizado
Sección desplegable<details> / <summary>necesitas un grupo de acordeón animado
Pestañas(no hay elemento nativo)siempre personalizado — sigue el patrón de pestañas ARIA

El ejemplo de slider a continuación en realidad envuelve el <input type="range"> nativo, que es el enfoque recomendado. El modal y las pestañas se construyen desde cero para que puedas ver las partes en movimiento, pero en producción prefiere <dialog> para modales.

Buenas Prácticas

  1. Usa HTML Semántico: Asegúrate de que tu estructura HTML sea significativa y accesible.
  2. Separa Responsabilidades: Mantén HTML, CSS y JavaScript separados para mantener un código limpio y manejable.
  3. Accesibilidad: Asegúrate de que los elementos interactivos sean accesibles para todos los usuarios, incluidos los que usan lectores de pantalla.
  4. Optimización del Rendimiento: Minimiza la manipulación del DOM y optimiza JavaScript para garantizar interacciones fluidas.
  5. Diseño Responsivo: Asegúrate de que los elementos interactivos funcionen bien en diferentes tamaños de pantalla y dispositivos.

Creación de Widgets Personalizados

Advertencia

Para aplicaciones en producción, considera primero los elementos HTML nativos: <dialog> para modales y <details> / <summary> para contenido desplegable. Te proporcionan gestión del foco, la tecla Escape y soporte de teclado de forma gratuita.

Slider Personalizado

Un slider personalizado permite a los usuarios seleccionar un valor de un rango. A continuación se muestra cómo puedes crear uno utilizando HTML, CSS y JavaScript.

Ejemplo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Slider</title>
    <style>
        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        #slider {
            width: 200px;
        }
    </style>
</head>
<body>
    <div class="slider-container">
        <input type="range" id="slider" min="0" max="100" value="50" aria-label="Value slider" aria-describedby="slider-value" />
        <span id="slider-value">50</span>
    </div>
    <script>
        document.getElementById('slider').addEventListener('input', function() {
            document.getElementById('slider-value').textContent = this.value;
        });
    </script>
</body>
</html>

Este ejemplo crea un slider sencillo con un rango de entrada y un span para mostrar el valor actual. El JavaScript actualiza el contenido de texto del span a medida que el slider se mueve.

Los modales se utilizan para mostrar contenido en una superposición. A continuación se muestra cómo crear un modal personalizado.

Ejemplo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Modal</title>
    <style>
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            justify-content: center;
            align-items: center;
        }

        .modal-content {
            background-color: #fff;
            padding: 20px;
            border-radius: 5px;
            text-align: center;
        }

        .close {
            position: absolute;
            top: 10px;
            right: 10px;
            font-size: 20px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <button id="open-modal">Open Modal</button>
    <div id="modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" tabindex="-1">
        <div class="modal-content">
            <span id="close-modal" class="close" aria-label="Close modal">&times;</span>
            <h2 id="modal-title">Custom Modal</h2>
            <p>This is a custom modal!</p>
        </div>
    </div>
    <script>
        const modal = document.getElementById('modal');
        const openBtn = document.getElementById('open-modal');
        const closeBtn = document.getElementById('close-modal');

        openBtn.addEventListener('click', () => {
            modal.style.display = 'flex';
            closeBtn.focus();
        });

        closeBtn.addEventListener('click', () => {
            modal.style.display = 'none';
            openBtn.focus();
        });

        window.addEventListener('keydown', (e) => {
            if (modal.style.display === 'flex' && e.key === 'Escape') {
                modal.style.display = 'none';
                openBtn.focus();
            }
        });

        modal.addEventListener('click', (event) => {
            if (event.target === modal) {
                modal.style.display = 'none';
                openBtn.focus();
            }
        });
    </script>
</body>
</html>

Este ejemplo demuestra cómo crear un modal que puede abrirse y cerrarse usando JavaScript. El modal muestra una superposición y un cuadro de contenido, que puede cerrarse haciendo clic en un botón, presionando Escape o haciendo clic en la superposición. El foco se gestiona para garantizar que los usuarios de teclado puedan navegar por el diálogo.

Pestañas Personalizadas

Las pestañas permiten a los usuarios alternar entre diferentes secciones de contenido. A continuación se muestra cómo crear pestañas personalizadas.

Ejemplo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Tabs</title>
    <style>
        .tabs {
            display: flex;
            gap: 10px;
        }

        .tab-button {
            padding: 10px 20px;
            cursor: pointer;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            border-radius: 5px;
        }

        .tab-button.active {
            background-color: #fff;
            border-bottom: 2px solid #000;
        }

        .tab-content {
            display: none;
            margin-top: 20px;
        }

        .tab-content.active {
            display: block;
        }
    </style>
</head>
<body>
    <div class="tabs" role="tablist">
        <button class="tab-button active" role="tab" aria-selected="true" data-tab="tab1">Tab 1</button>
        <button class="tab-button" role="tab" aria-selected="false" data-tab="tab2">Tab 2</button>
        <button class="tab-button" role="tab" aria-selected="false" data-tab="tab3">Tab 3</button>
    </div>
    <div class="tab-content active" role="tabpanel" aria-labelledby="tab1">
        <p>Content for Tab 1</p>
    </div>
    <div class="tab-content" role="tabpanel" aria-labelledby="tab2">
        <p>Content for Tab 2</p>
    </div>
    <div class="tab-content" role="tabpanel" aria-labelledby="tab3">
        <p>Content for Tab 3</p>
    </div>
    <script>
        // Array.from is important: querySelectorAll returns a NodeList,
        // which has no .indexOf method. We need a real array for the
        // arrow-key navigation below.
        const buttons = Array.from(document.querySelectorAll('.tab-button'));
        const contents = document.querySelectorAll('.tab-content');

        function activateTab(clickedBtn) {
            buttons.forEach(btn => {
                btn.classList.remove('active');
                btn.setAttribute('aria-selected', 'false');
            });
            contents.forEach(content => content.classList.remove('active'));

            clickedBtn.classList.add('active');
            clickedBtn.setAttribute('aria-selected', 'true');
            document.getElementById(clickedBtn.dataset.tab).classList.add('active');
        }

        buttons.forEach(button => {
            button.addEventListener('click', () => activateTab(button));
            button.addEventListener('keydown', (e) => {
                let nextIndex;
                if (e.key === 'ArrowRight') nextIndex = (buttons.indexOf(button) + 1) % buttons.length;
                else if (e.key === 'ArrowLeft') nextIndex = (buttons.indexOf(button) - 1 + buttons.length) % buttons.length;
                else return;
                e.preventDefault();
                buttons[nextIndex].focus();
                activateTab(buttons[nextIndex]);
            });
        });
    </script>
</body>
</html>

Este ejemplo crea una interfaz de pestañas. Hacer clic en un botón de pestaña o usar las teclas de flecha izquierda/derecha mostrará el contenido correspondiente y ocultará los demás. El manejo de las teclas de flecha sigue el patrón de pestañas WAI-ARIA, que es lo que permite a los usuarios de teclado moverse entre pestañas sin ratón — una parte clave de la accesibilidad del DOM.

Acordeón con <details> nativo

No todo widget desplegable necesita JavaScript. El elemento <details> nativo te ofrece un interruptor de apertura/cierre, soporte de teclado y la semántica correcta sin ningún script. Úsalo para FAQs y paneles de divulgación.

Ejemplo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Accordion</title>
</head>
<body>
    <details>
        <summary>What is a widget?</summary>
        <p>A self-contained interactive UI component.</p>
    </details>
    <details>
        <summary>Do I always need JavaScript?</summary>
        <p>No — native elements like this one work without any script.</p>
    </details>
    <script>
        // Optional: react when a panel opens (e.g. lazy-load content).
        document.querySelectorAll('details').forEach((d) => {
            d.addEventListener('toggle', () => {
                console.log(d.open ? 'opened' : 'closed');
            });
        });
    </script>
</body>
</html>

El evento toggle se dispara cada vez que el usuario expande o colapsa un panel, por lo que puedes cargar contenido de forma diferida o registrar análisis. Recurre a un acordeón con script solo cuando necesites animación o el comportamiento de "solo un panel abierto a la vez".

Uso de las API de HTML5

Introducción a las API de HTML5

Las API de HTML5 proporcionan potentes funciones que mejoran las aplicaciones web. Una de las API de HTML5 más versátiles es la Canvas API, que permite la creación de gráficos dinámicos.

Uso de la Canvas API

La Canvas API te permite dibujar gráficos directamente en una página web. A continuación se muestra un ejemplo básico de cómo usar la Canvas API.

Ejemplo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas API Example</title>
</head>
<body>
    <canvas id="myCanvas" width="400" height="400" style="border:1px solid #000;"></canvas>
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');

        // Draw a rectangle
        ctx.fillStyle = '#FF0000';
        ctx.fillRect(50, 50, 150, 100);

        // Draw a circle
        ctx.beginPath();
        ctx.arc(200, 200, 40, 0, 2 * Math.PI);
        ctx.fillStyle = '#00FF00';
        ctx.fill();

        // Draw text
        ctx.font = '20px Arial';
        ctx.fillStyle = '#0000FF';
        ctx.fillText('Hello Canvas', 100, 300);
    </script>
</body>
</html>

Este ejemplo demuestra las funciones básicas de dibujo de la Canvas API:

  • fillRect(x, y, width, height) dibuja un rectángulo relleno cuya esquina superior izquierda está en (x, y).
  • arc(x, y, radius, startAngle, endAngle) agrega un trazado circular centrado en (x, y); los ángulos están en radianes, por lo que un círculo completo va de 0 a 2 * Math.PI. Debes llamar a beginPath() antes y a fill() (o stroke()) después.
  • fillText(text, x, y) dibuja texto, donde (x, y) es el inicio de la línea base, no la esquina superior izquierda.

Un error común: establece fillStyle antes de la llamada de dibujo a la que debe afectar — el canvas mantiene el último color que estableciste, por lo que reutilizar un color en varias formas es fácil de hacer por accidente. Para animaciones, limpia el fotograma anterior con ctx.clearRect(0, 0, canvas.width, canvas.height) y vuelve a dibujar dentro de requestAnimationFrame. Establecer el tamaño del canvas en CSS en lugar de los atributos width/height estira el dibujo — siempre dimensiónalo con los atributos.

Información

Asegúrate siempre de que tus elementos interactivos sean accesibles. Usa roles y propiedades ARIA, HTML semántico, y garantiza la navegabilidad por teclado para proporcionar una experiencia de usuario inclusiva para todos. Esto no solo mejora la accesibilidad, sino que también mejora la usabilidad general y el SEO.

Conclusión

Los widgets personalizados — sliders, modales, pestañas, acordeones — y las API de HTML5 como Canvas te permiten construir las superficies interactivas en las que se apoyan las aplicaciones web modernas. La lección recurrente en todos ellos es la misma: empieza desde un elemento nativo cuando exista uno, luego añade JavaScript solo para el comportamiento que el navegador no te proporciona, y nunca omitas el soporte de teclado y ARIA.

Para profundizar más, consulta las técnicas de manipulación del DOM para los bloques de construcción que utilizan estos widgets, el manejo de eventos en el DOM para saber cómo se conecta la entrada del usuario, y la optimización del rendimiento del DOM para mantener las interacciones fluidas.

Práctica

Práctica
¿Cuáles de las siguientes afirmaciones sobre los elementos interactivos y widgets son verdaderas?
¿Cuáles de las siguientes afirmaciones sobre los elementos interactivos y widgets son verdaderas?
Was this page helpful?