W3docs

Elementos Interativos e Widgets no Desenvolvimento Web

Aprenda a criar widgets personalizados em JavaScript — sliders, modais, abas e acordeões — além da Canvas API para gráficos dinâmicos, com exemplos acessíveis via teclado.

Criar widgets personalizados e aproveitar as APIs do HTML pode melhorar significativamente a interatividade e a experiência do usuário em suas aplicações web. Este guia fornece instruções passo a passo para criar elementos interativos personalizados como sliders, modais e abas, e apresenta APIs do HTML5, como a Canvas API, para criação de gráficos dinâmicos.

Introdução

Um widget é um componente de interface interativo e autossuficiente — um slider, uma caixa de diálogo modal, um painel com abas, um acordeão — que o usuário pode operar para alterar o que vê ou para inserir dados. Os navegadores oferecem alguns widgets nativos (<input type="range">, <dialog>, <details>), mas muitas vezes você precisará criar os seus próprios quando precisar de comportamento, estilo ou layout personalizados que os elementos nativos não fornecem.

Este guia mostra como criar os três widgets mais usados — um slider, um modal e um conjunto de abas — e apresenta a Canvas API, um recurso do HTML5 para desenhar gráficos dinâmicos. Cada exemplo conecta o comportamento ao DOM e ao tratamento de eventos, por isso é útil estar familiarizado com seleção de elementos e eventos de navegador antes de começar.

Widget nativo ou widget personalizado?

Prefira um elemento nativo antes de escrever JavaScript. Widgets nativos são acessíveis, operáveis pelo teclado e compatíveis com formulários por padrão, e continuam funcionando mesmo quando seu script falha ao carregar.

NecessidadeElemento nativoCrie um personalizado quando…
Selecionar um valor em um intervalo<input type="range">você precisa de controle com dois cursores ou não linear
Exibir um diálogo bloqueante<dialog> + showModal()você precisa de layout ou animação totalmente personalizados
Seção recolhível<details> / <summary>você precisa de um grupo de acordeão animado
Abas(nenhum elemento nativo)sempre personalizado — siga o padrão ARIA de abas

O exemplo de slider abaixo envolve o elemento nativo <input type="range">, que é a abordagem recomendada. O modal e as abas são criados do zero para que você possa ver as peças em movimento, mas em produção prefira <dialog> para modais.

Boas Práticas

  1. Use HTML Semântico: Certifique-se de que a estrutura do seu HTML seja significativa e acessível.
  2. Separação de Responsabilidades: Mantenha HTML, CSS e JavaScript separados para manter o código limpo e gerenciável.
  3. Acessibilidade: Garanta que os elementos interativos sejam acessíveis a todos os usuários, incluindo aqueles que usam leitores de tela.
  4. Otimização de Desempenho: Minimize a manipulação do DOM e otimize o JavaScript para garantir interações suaves.
  5. Design Responsivo: Garanta que os elementos interativos funcionem bem em diferentes tamanhos de tela e dispositivos.

Criando Widgets Personalizados

Aviso

Para aplicações em produção, considere primeiro os elementos HTML nativos: <dialog> para modais e <details> / <summary> para conteúdo recolhível. Eles oferecem gerenciamento de foco, a tecla Escape e suporte ao teclado gratuitamente.

Slider Personalizado

Um slider personalizado permite que os usuários selecionem um valor de um intervalo. Veja como criar um usando HTML, CSS e JavaScript.

Exemplo

<!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 exemplo cria um slider simples com um intervalo de entrada e um span para exibir o valor atual. O JavaScript atualiza o conteúdo de texto do span conforme o slider é movido.

Os modais são usados para exibir conteúdo em uma sobreposição. Veja como criar um modal personalizado.

Exemplo

<!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 exemplo demonstra como criar um modal que pode ser aberto e fechado usando JavaScript. O modal exibe uma sobreposição e uma caixa de conteúdo, que pode ser fechada clicando em um botão, pressionando Escape ou clicando na sobreposição. O foco é gerenciado para garantir que usuários de teclado possam navegar pelo diálogo.

Abas Personalizadas

As abas permitem que os usuários alternem entre diferentes seções de conteúdo. Veja como criar abas personalizadas.

Exemplo

<!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 exemplo cria uma interface com abas. Clicar em um botão de aba ou usar as teclas de seta esquerda/direita exibirá o conteúdo correspondente e ocultará os demais. O tratamento das teclas de seta segue o padrão WAI-ARIA de abas, que permite que usuários de teclado se movam entre as abas sem mouse — uma parte fundamental da acessibilidade no DOM.

Acordeão com <details> nativo

Nem todo widget recolhível precisa de JavaScript. O elemento nativo <details> oferece alternância de abertura/fechamento, suporte ao teclado e a semântica correta com zero código. Use-o para FAQs e painéis de divulgação.

Exemplo

<!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>

O evento toggle é disparado sempre que o usuário expande ou recolhe um painel, permitindo que você carregue conteúdo de forma lazy ou rastreie análises. Use um acordeão com script apenas quando precisar de animação ou do comportamento "apenas um painel aberto por vez".

Usando APIs do HTML5

Introdução às APIs do HTML5

As APIs do HTML5 fornecem recursos poderosos que aprimoram as aplicações web. Uma das APIs do HTML5 mais versáteis é a Canvas API, que permite a criação de gráficos dinâmicos.

Usando a Canvas API

A Canvas API permite desenhar gráficos diretamente em uma página web. Veja um exemplo básico de como usar a Canvas API.

Exemplo

<!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 exemplo demonstra as funções básicas de desenho da Canvas API:

  • fillRect(x, y, width, height) desenha um retângulo preenchido cujo canto superior esquerdo está em (x, y).
  • arc(x, y, radius, startAngle, endAngle) adiciona um caminho de círculo centralizado em (x, y); os ângulos são em radianos, portanto um círculo completo vai de 0 a 2 * Math.PI. Você deve chamar beginPath() antes e fill() (ou stroke()) depois.
  • fillText(text, x, y) desenha texto, onde (x, y) é o início da linha de base, não o canto superior esquerdo.

Um erro comum: defina fillStyle antes da chamada de desenho que ele deve afetar — o canvas mantém a última cor definida, por isso é fácil reutilizar acidentalmente uma cor em várias formas. Para animações, limpe o frame anterior com ctx.clearRect(0, 0, canvas.width, canvas.height) e redesenhe dentro de requestAnimationFrame. Definir o tamanho do canvas via CSS em vez dos atributos width/height estica o desenho — sempre defina o tamanho com os atributos.

Informação

Sempre garanta que seus elementos interativos sejam acessíveis. Use roles e propriedades ARIA, HTML semântico, e assegure a navegabilidade pelo teclado para proporcionar uma experiência de usuário inclusiva a todos. Isso não só melhora a acessibilidade, mas também aprimora a usabilidade geral e o SEO.

Conclusão

Widgets personalizados — sliders, modais, abas, acordeões — e APIs do HTML5 como Canvas permitem criar as interfaces interativas das quais os aplicativos web modernos dependem. A lição recorrente em todos eles é a mesma: comece com um elemento nativo quando existir um, depois adicione JavaScript apenas para o comportamento que o navegador não oferece, e nunca omita o suporte ao teclado e ao ARIA.

Para ir mais longe, consulte técnicas de manipulação do DOM para os blocos de construção usados por esses widgets, tratamento de eventos no DOM para entender como a entrada do usuário é conectada, e otimização de desempenho do DOM para manter as interações suaves.

Prática

Prática
Quais das seguintes afirmações sobre elementos interativos e widgets são verdadeiras?
Quais das seguintes afirmações sobre elementos interativos e widgets são verdadeiras?
Was this page helpful?