W3docs

Interactive Elements and Widgets in Web Development

Learn to build accessible custom widgets in JavaScript — sliders, modals, tabs, and accordions — plus the Canvas API for dynamic graphics.

Creating custom widgets and leveraging HTML APIs can significantly enhance the interactivity and user experience of your web applications. This guide provides step-by-step instructions for building custom interactive elements like sliders, modals, and tabs, and introduces you to HTML5 APIs such as the Canvas API for creating dynamic graphics.

Introduction

A widget is a self-contained interactive UI component — a slider, a modal dialog, a tabbed panel, an accordion — that the user can operate to change what they see or to enter data. Browsers ship a handful of widgets natively (<input type="range">, <dialog>, <details>), but you will often build your own when you need custom behavior, styling, or layout that the built-in elements do not provide.

This guide shows how to build the three widgets you will reach for most often — a slider, a modal, and a set of tabs — and then introduces the Canvas API, an HTML5 feature for drawing dynamic graphics. Every example wires up the behavior with the DOM and event handling, so it helps to be comfortable with selecting elements and browser events first.

Native widget or custom widget?

Reach for a native element before writing JavaScript. Native widgets are accessible, keyboard-operable, and form-aware out of the box, and they keep working when your script fails to load.

NeedNative elementBuild custom when…
Pick a value in a range<input type="range">you need a two-thumb / non-linear control
Show a blocking dialog<dialog> + showModal()you need fully custom layout or animation
Collapsible section<details> / <summary>you need an animated accordion group
Tabs(no native element)always custom — follow the ARIA tabs pattern

The slider example below actually wraps the native <input type="range">, which is the recommended approach. The modal and tabs are built from scratch so you can see the moving parts, but in production prefer <dialog> for modals.

Best Practices

  1. Use Semantic HTML: Ensure that your HTML structure is meaningful and accessible.
  2. Separate Concerns: Keep HTML, CSS, and JavaScript separate to maintain clean and manageable code.
  3. Accessibility: Ensure that interactive elements are accessible to all users, including those using screen readers.
  4. Performance Optimization: Minimize DOM manipulation and optimize JavaScript to ensure smooth interactions.
  5. Responsive Design: Ensure that interactive elements work well on different screen sizes and devices.

Creating Custom Widgets

Warning

For production applications, consider native HTML elements first: <dialog> for modals, and <details> / <summary> for collapsible content. They give you focus management, the Escape key, and keyboard support for free.

Custom Slider

A custom slider allows users to select a value from a range. Here’s how you can create one using HTML, CSS, and JavaScript.

Example

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

This example creates a simple slider with an input range and a span to display the current value. The JavaScript updates the span’s text content as the slider moves.

Custom Modal

Modals are used to display content in an overlay. Here’s how to create a custom modal.

Example

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

This example demonstrates how to create a modal that can be opened and closed using JavaScript. The modal displays an overlay and a content box, which can be closed by clicking a button, pressing Escape, or clicking the overlay. Focus is managed to ensure keyboard users can navigate the dialog.

Custom Tabs

Tabs allow users to switch between different sections of content. Here’s how to create custom tabs.

Example

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

This example creates a tabbed interface. Clicking on a tab button or using the left/right arrow keys will display the corresponding content and hide the others. The arrow-key handling follows the WAI-ARIA tabs pattern, which is what lets keyboard users move between tabs without a mouse — a key part of DOM accessibility.

Accordion with native <details>

Not every collapsible widget needs JavaScript. The native <details> element gives you an open/close toggle, keyboard support, and the right semantics with zero script. Use it for FAQs and disclosure panels.

Example

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

The toggle event fires whenever the user expands or collapses a panel, so you can lazy-load content or track analytics. Reach for a scripted accordion only when you need animation or "only one panel open at a time" behavior.

Using HTML5 APIs

Introduction to HTML5 APIs

HTML5 APIs provide powerful features that enhance web applications. One of the most versatile HTML5 APIs is the Canvas API, which allows for dynamic graphics creation.

Using the Canvas API

The Canvas API enables you to draw graphics directly on a web page. Here’s a basic example of how to use the Canvas API.

Example

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

This example demonstrates basic drawing functions of the Canvas API:

  • fillRect(x, y, width, height) draws a filled rectangle whose top-left corner is at (x, y).
  • arc(x, y, radius, startAngle, endAngle) adds a circle path centered at (x, y); angles are in radians, so a full circle is 0 to 2 * Math.PI. You must call beginPath() before it and fill() (or stroke()) after.
  • fillText(text, x, y) draws text, where (x, y) is the baseline start, not the top-left.

A common gotcha: set fillStyle before the drawing call it should affect — the canvas keeps the last color you set, so reusing one color across shapes is easy to do by accident. For animations, clear the previous frame with ctx.clearRect(0, 0, canvas.width, canvas.height) and redraw inside requestAnimationFrame. Setting the canvas size in CSS instead of the width/height attributes stretches the drawing — always size it with the attributes.

Info

Always ensure your interactive elements are accessible. Use ARIA roles and properties, semantic HTML, and ensure keyboard navigability to provide an inclusive user experience for all users. This not only improves accessibility but also enhances overall usability and SEO.

Conclusion

Custom widgets — sliders, modals, tabs, accordions — and HTML5 APIs like Canvas let you build the interactive surfaces modern web apps rely on. The recurring lesson across all of them is the same: start from a native element when one exists, then add JavaScript only for the behavior the browser does not give you, and never skip keyboard and ARIA support.

To go further, see DOM manipulation techniques for the building blocks these widgets use, event handling in the DOM for how user input is wired up, and DOM performance optimization to keep interactions smooth.

Practice

Practice
Which of the following statements about interactive elements and widgets are true?
Which of the following statements about interactive elements and widgets are true?
Was this page helpful?