Tabbed Interface Web Component

A web component that transforms heading-structured content into an accessible tabbed interface. See the README for installation and API documentation.

Basic Example

The component automatically converts headings into tabs with accessible keyboard navigation and ARIA attributes.

Basic Usage

Simply wrap your heading-structured content in the <tabbed-interface> element.

<tabbed-interface>
  <h3>Getting Started</h3>
  <p>First tab content.</p>

  <h3>Features</h3>
  <p>Second tab content.</p>

  <h3>Installation</h3>
  <p>Third tab content.</p>
</tabbed-interface>

Getting Started

This is the first tab panel. The component automatically creates tabs from headings.

Headers are hidden by default in panels.

Features

  • Automatic tab creation from headings
  • Full keyboard navigation support
  • ARIA attributes for accessibility
  • CSS custom properties for styling

Installation

npm install @aarongustafson/tabbed-interface

Then import in your JavaScript:

import '@aarongustafson/tabbed-interface';

Attributes

The component supports several attributes to customize its behavior.

Auto-Activation

Add the auto-activate attribute to make tabs activate on focus. By default (without the attribute), tabs require Enter or Space to activate, following the manual activation pattern for better accessibility.

<tabbed-interface auto-activate>
  <h3>Tab One</h3>
  <p>Content here.</p>

  <h3>Tab Two</h3>
  <p>More content.</p>
</tabbed-interface>
Try it: Use arrow keys to focus different tabs. They activate automatically on focus.

Auto Tab 1

With auto-activation, tabs activate immediately when you focus them with arrow keys.

Auto Tab 2

This can be convenient for quick navigation when you have few tabs.

Auto Tab 3

Just navigate with arrow keys and the panel changes automatically.

Visible Headers

Use show-headers to keep headings visible in tab panels.

<tabbed-interface show-headers>
  <h3>Overview</h3>
  <p>Content here.</p>

  <h3>Details</h3>
  <p>More content.</p>
</tabbed-interface>

Overview

The heading is now visible inside the panel along with the tab.

Details

This can be useful for certain design patterns or when you want redundancy.

Tab List Position

Use tablist-after to place tabs below the content.

<tabbed-interface tablist-after>
  <h3>First</h3>
  <p>Content appears above tabs.</p>

  <h3>Second</h3>
  <p>More content.</p>
</tabbed-interface>

First Section

The tabs appear below the content when using tablist-after.

Second Section

This can be useful for certain layouts or design patterns.

Default Tab

Use default-tab to set which tab is active initially (by index or heading ID).

<tabbed-interface default-tab="1">
  <h3>Tab 0</h3>
  <p>First tab.</p>

  <h3>Tab 1</h3>
  <p>This tab is active by default.</p>

  <h3>Tab 2</h3>
  <p>Third tab.</p>
</tabbed-interface>

Tab 0

First tab content.

Tab 1

This tab is active by default because of default-tab="1".

Tab 2

Third tab content.

Styling Variants

Style the component using CSS ::part() selectors for complete visual control.

Pill-Style Tabs

Create rounded pill-style tabs using ::part() selectors.

tabbed-interface.pills::part(tablist) {
  gap: 8px;
  margin: 0 0 1em 0;
}

tabbed-interface.pills::part(tab) {
  background-color: ButtonFace;
  border: none;
  border-radius: 999px;
  padding: 0.5em 1.5em;
}

tabbed-interface.pills::part(tab):hover {
  background-color: SelectedItem;
}

tabbed-interface.pills::part(tab selected),
tabbed-interface.pills::part(tabpanel) {
  background-color: Highlight;
  color: HighlightText;
}

tabbed-interface.pills::part(tabpanel) {
  border: none;
}

Tab One

The pill style is achieved entirely through CSS ::part() selectors.

Tab Two

No JavaScript changes needed!

Tab Three

Fully customizable appearance.

Minimal Style

A clean, minimal style with an underline indicator.

tabbed-interface.minimal::part(tablist) {
  gap: 0;
  margin: 0 0 -1px 0;
}

tabbed-interface.minimal::part(tab) {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0.75em 1em;
}

tabbed-interface.minimal::part(tab):hover {
  background: ButtonFace;
}

tabbed-interface.minimal::part(tab selected) {
  background: transparent;
  color: LinkText;
  border-bottom: 2px solid LinkText;
}

tabbed-interface.minimal::part(tabpanel) {
  padding: 1.5em 0;
  border: none;
  border-top: 2px solid AccentColor;
}

Design

A minimal design with just an underline for the active tab.

Develop

Clean and simple, great for modern interfaces.

Deploy

Lightweight visual treatment.

Advanced Features

The component includes several advanced features for enhanced functionality.

Custom Tab Titles

Use data-tab-short-name to show shorter labels in tabs while keeping full headings in panels. The full text is available as aria-label for screen readers.

<tabbed-interface>
  <h3 data-tab-short-name="Short">
    Very Long Section Title
  </h3>
  <p>Content here.</p>

  <h3 data-tab-short-name="Emoji 🎉">
    Plain Heading
  </h3>
  <p>More content.</p>
</tabbed-interface>

Very Long Section Title That Wouldn't Fit Well In a Tab

The tab shows "Short" but the full heading is visible in the panel and available to screen readers.

Plain Heading

You can even add emoji or other content to your tab titles!

API Reference Guide

Great for abbreviating long titles.

Event Handling

Listen for tabbed-interface:change events to respond to tab changes.

document.querySelector('tabbed-interface')
  .addEventListener(
    'tabbed-interface:change',
    (event) => {
      console.log(event.detail);
    }
  );

Introduction

Click between tabs and watch the output below.

Details

The event fires whenever the active tab changes and includes useful details.

API

Event detail includes tabIndex, tabId, and tabpanelId.

Waiting for tab change...

Programmatic Control

Control tabs programmatically using the JavaScript API.

const tabs = document.querySelector(
  'tabbed-interface'
);

// Navigate
tabs.next();
tabs.previous();
tabs.first();
tabs.last();

// Set active tab directly
tabs.activeIndex = 2;

Panel A

First panel content with some text.

Panel B

Second panel content with more information.

Panel C

Third panel content with additional details.

Panel D

Fourth panel content to demonstrate navigation.

Current tab: 0

Hash Navigation

The component supports URL hash navigation for deep linking to specific tabs.

<a href="#features">
  Go to Features
</a>

<tabbed-interface>
  <h3 id="intro">Introduction</h3>
  <p>Content...</p>

  <h3 id="features">Features</h3>
  <p>Content...</p>
</tabbed-interface>

Jump to Start | Jump to Middle | Jump to End

Start

This tab can be accessed via #hash-start.

Middle

This tab can be accessed via #hash-middle.

End

This tab can be accessed via #hash-end.

Accessibility

The component is built with accessibility as a core feature.

Keyboard Navigation

Full keyboard support following ARIA Authoring Practices.

Arrow Keys    Navigate tabs
Home          First tab
End           Last tab
Enter/Space   Activate tab and focus panel content
Try it: Click on any tab below, then use your keyboard to navigate.

Tab 1

Use Arrow keys to move between tabs. By default, you must press Enter or Space to activate a tab and view its content.

Tab 2

Press Home to jump to the first tab, or End to jump to the last.

Tab 3

After activating, Enter or Space will move focus to the first focusable element in the panel.

API Reference

For complete documentation of attributes, properties, methods, events, CSS custom properties, keyboard navigation, and browser support, see the README.