Tabs

Tabs organize content into separate views, allowing users to switch between related sections without leaving the page. They help reduce clutter and make complex interfaces more manageable.


Installation

Copy the tabs CSS from styles/docs.css or include the Standard stylesheet:

<link rel="stylesheet" href="standard.min.css">

Then use tabs classes in your HTML:

<div class="Tabs">
    <div class="Tabs-list" role="tablist">
        <button class="Tabs-tab active" role="tab" aria-selected="true">Tab One</button>
        <button class="Tabs-tab" role="tab" aria-selected="false">Tab Two</button>
    </div>
    <div class="Tabs-panel active" role="tabpanel">Content here...</div>
</div>

Usage

The base .Tabs class provides the container. The .Tabs-list holds tab buttons, and .Tabs-panel contains the content for each tab.

Overview content goes here. This is the first panel with introductory information.

<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Overview</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Features</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Pricing</button>
</div>
<div class="Tabs-panel active" role="tabpanel">
<p>Overview content goes here. This is the first panel with introductory information.</p>
</div>
</div>

Variants

All Variants

Default
Contained
Pills
<div style="display: flex; flex-direction: column; gap: var(--space-8);">
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Default</div>
<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
</div>
</div>
</div>
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Contained</div>
<div class="Tabs Tabs--contained">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
</div>
</div>
</div>
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Pills</div>
<div class="Tabs Tabs--pills">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Tab</button>
</div>
</div>
</div>
</div>

Default

The default tabs style features an underline indicator on the active tab. Best for primary navigation within a page.

Overview content goes here. This is the first panel.

<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Overview</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Features</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Pricing</button>
</div>
<div class="Tabs-panel active" role="tabpanel">
<p>Overview content goes here. This is the first panel.</p>
</div>
</div>

Contained

Contained tabs have a visible container boundary, making them ideal for code blocks, embedded content, or when tabs need visual separation from surrounding content.

<button class="Button">Click me</button>
<div class="Tabs Tabs--contained">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">HTML</button>
<button class="Tabs-tab" role="tab" aria-selected="false">CSS</button>
<button class="Tabs-tab" role="tab" aria-selected="false">JavaScript</button>
</div>
<div class="Tabs-panel active" role="tabpanel">
<pre><code>&lt;button class="Button"&gt;Click me&lt;/button&gt;</code></pre>
</div>
</div>

Pills

Pills-style tabs are more compact and work well for filters, categories, or segmented controls.

<div class="Tabs Tabs--pills">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">All</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Active</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Completed</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Archived</button>
</div>
</div>

With Icons

Tabs can include icons for visual reinforcement. Icons can appear alone or alongside text.

Icon with Text

<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">
<i class="ph ph-house"></i>
<span>Home</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
<i class="ph ph-gear"></i>
<span>Settings</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
<i class="ph ph-user"></i>
<span>Profile</span>
</button>
</div>
</div>

Icon Only

For compact interfaces where icons are self-explanatory. Always include aria-label for accessibility.

<div class="Tabs Tabs--pills">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true" aria-label="List view">
<i class="ph ph-list"></i>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false" aria-label="Grid view">
<i class="ph ph-squares-four"></i>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false" aria-label="Column view">
<i class="ph ph-columns"></i>
</button>
</div>
</div>

States

Disabled

Disabled tabs indicate a section is unavailable. Use sparingly and provide context for why.

<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Active</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Available</button>
<button class="Tabs-tab" role="tab" aria-selected="false" disabled>Premium Only</button>
</div>
</div>

With Badge

Show counts or status indicators with badges inside tabs.

<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">
Inbox
<span class="Badge Badge--primary" style="margin-left: var(--space-2);">12</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
Sent
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
Drafts
<span class="Badge Badge--secondary" style="margin-left: var(--space-2);">3</span>
</button>
</div>
</div>

Sizes

Tabs come in three sizes to fit different contexts.

Small
Default
Large
<div style="display: flex; flex-direction: column; gap: var(--space-6);">
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Small</div>
<div class="Tabs Tabs--small">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Details</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Activity</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Comments</button>
</div>
</div>
</div>
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Default</div>
<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Details</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Activity</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Comments</button>
</div>
</div>
</div>
<div>
<div style="font-size: 0.75rem; color: var(--fg-3); margin-bottom: var(--space-2);">Large</div>
<div class="Tabs Tabs--large">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">Details</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Activity</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Comments</button>
</div>
</div>
</div>
</div>

Full Width

Tabs can stretch to fill their container width. Useful for mobile layouts.

<div class="Tabs Tabs--fullWidth">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">For You</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Following</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Trending</button>
</div>
</div>

JavaScript

Tabs require minimal JavaScript for interactivity:

document.querySelectorAll('.Tabs').forEach(tabs => {
    const tabList = tabs.querySelector('.Tabs-list');
    const tabButtons = tabs.querySelectorAll('.Tabs-tab');
    const panels = tabs.querySelectorAll('.Tabs-panel');
    
    tabList.addEventListener('click', (e) => {
        const tab = e.target.closest('.Tabs-tab');
        if (!tab || tab.disabled) return;
        
        // Update tab states
        tabButtons.forEach(t => {
            t.classList.remove('active');
            t.setAttribute('aria-selected', 'false');
        });
        tab.classList.add('active');
        tab.setAttribute('aria-selected', 'true');
        
        // Update panel visibility
        const index = Array.from(tabButtons).indexOf(tab);
        panels.forEach((panel, i) => {
            panel.classList.toggle('active', i === index);
        });
    });
    
    // Keyboard navigation
    tabList.addEventListener('keydown', (e) => {
        const tabs = Array.from(tabButtons).filter(t => !t.disabled);
        const current = tabs.findIndex(t => t.classList.contains('active'));
        let next;
        
        switch(e.key) {
            case 'ArrowRight':
            case 'ArrowDown':
                next = (current + 1) % tabs.length;
                break;
            case 'ArrowLeft':
            case 'ArrowUp':
                next = (current - 1 + tabs.length) % tabs.length;
                break;
            case 'Home':
                next = 0;
                break;
            case 'End':
                next = tabs.length - 1;
                break;
            default:
                return;
        }
        
        e.preventDefault();
        tabs[next].click();
        tabs[next].focus();
    });
});

Common Patterns

Settings Page

Account Settings

Manage your account information and preferences.

<div style="max-width: 600px;">
<div class="Tabs">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">
<i class="ph ph-user"></i>
<span>Account</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
<i class="ph ph-bell"></i>
<span>Notifications</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
<i class="ph ph-lock"></i>
<span>Privacy</span>
</button>
<button class="Tabs-tab" role="tab" aria-selected="false">
<i class="ph ph-palette"></i>
<span>Appearance</span>
</button>
</div>
<div class="Tabs-panel active" role="tabpanel">
<div style="padding: var(--space-4) 0;">
<h3 style="margin: 0 0 var(--space-3);">Account Settings</h3>
<p style="color: var(--fg-3); margin: 0;">Manage your account information and preferences.</p>
</div>
</div>
</div>
</div>

Code Viewer

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My App</title>
  </head>
</html>
<div class="Tabs Tabs--contained">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">index.html</button>
<button class="Tabs-tab" role="tab" aria-selected="false">styles.css</button>
<button class="Tabs-tab" role="tab" aria-selected="false">app.js</button>
</div>
<div class="Tabs-panel active" role="tabpanel" style="background: var(--bg-s); padding: var(--space-4);">
<pre style="margin: 0; font-size: 0.875rem;"><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;title&gt;My App&lt;/title&gt;
&lt;/head&gt;
&lt;/html&gt;</code></pre>
</div>
</div>

Filter Bar

<div style="display: flex; align-items: center; gap: var(--space-4);">
<div class="Tabs Tabs--pills">
<div class="Tabs-list" role="tablist">
<button class="Tabs-tab active" role="tab" aria-selected="true">All</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Open</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Closed</button>
</div>
</div>
<div style="flex: 1;"></div>
<input type="search" class="Input Input--small" placeholder="Search..." style="max-width: 200px;">
</div>

Vertical Tabs

General Settings

Configure your general preferences and options.

<div class="Tabs Tabs--vertical" style="display: flex; gap: var(--space-4);">
<div class="Tabs-list" role="tablist" style="flex-direction: column; border-bottom: none; border-right: 1px solid var(--bd); padding-right: var(--space-4);">
<button class="Tabs-tab active" role="tab" aria-selected="true">General</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Security</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Billing</button>
<button class="Tabs-tab" role="tab" aria-selected="false">Team</button>
</div>
<div class="Tabs-panel active" role="tabpanel" style="flex: 1;">
<h4 style="margin: 0 0 var(--space-2);">General Settings</h4>
<p style="margin: 0; color: var(--fg-3);">Configure your general preferences and options.</p>
</div>
</div>

Customization

Override tab styles using CSS custom properties:

/* Custom accent color */
.Tabs-tab.active {
  --tab-active-color: oklch(55% 0.2 150);
  color: var(--tab-active-color);
  border-color: var(--tab-active-color);
}

/* Custom pill styling */
.Tabs--pills .Tabs-tab.active {
  --tab-pill-bg: oklch(55% 0.2 150);
  background-color: var(--tab-pill-bg);
  color: white;
}

/* Custom spacing */
.Tabs--compact .Tabs-tab {
  padding: var(--space-1) var(--space-2);
  font-size: 0.75rem;
}

/* Custom underline thickness */
.Tabs-tab.active {
  border-bottom-width: 3px;
}

Creating Custom Variants

/* Bordered variant */
.Tabs--bordered .Tabs-list {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-1);
  background: var(--bg-s);
}

.Tabs--bordered .Tabs-tab.active {
  background: var(--bg);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

/* Minimal variant */
.Tabs--minimal .Tabs-list {
  border-bottom: none;
  gap: var(--space-6);
}

.Tabs--minimal .Tabs-tab {
  padding: var(--space-2) 0;
  border-bottom: none;
  opacity: 0.6;
}

.Tabs--minimal .Tabs-tab.active {
  opacity: 1;
  font-weight: 600;
}

API Reference

Base Classes

Class Description
.Tabs Container for the tabs component (required)
.Tabs-list Container for tab buttons
.Tabs-tab Individual tab button
.Tabs-panel Content panel for each tab

Variant Classes

Class Description
.Tabs--contained Tabs with visible container boundary
.Tabs--pills Pill-style tab buttons
.Tabs--vertical Vertical tab layout

Size Classes

Class Description
.Tabs--small Compact tabs for tight spaces
.Tabs--large Larger tabs for primary navigation

Modifier Classes

Class Description
.Tabs--fullWidth Tabs stretch to fill container width
.active Active state for tab and panel

ARIA Attributes

Attribute Element Description
role="tablist" .Tabs-list Identifies the tab list container
role="tab" .Tabs-tab Identifies each tab button
role="tabpanel" .Tabs-panel Identifies the content panel
aria-selected .Tabs-tab "true" for active tab, "false" for others

CSS Reference

/* Base tabs */
.Tabs {
  display: flex;
  flex-direction: column;
}

/* Tab list */
.Tabs-list {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--bd);
}

/* Individual tab */
.Tabs-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--fg-3);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  white-space: nowrap;
}

.Tabs-tab:hover {
  color: var(--fg);
}

.Tabs-tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.Tabs-tab:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Tab panel */
.Tabs-panel {
  display: none;
  padding: var(--space-4) 0;
}

.Tabs-panel.active {
  display: block;
}

/* Contained variant */
.Tabs--contained {
  border: 1px solid var(--bd);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.Tabs--contained .Tabs-list {
  background: var(--bg-s);
}
.Tabs--contained .Tabs-tab.active {
  background: var(--bg);
  border-bottom-color: var(--bg);
}
.Tabs--contained .Tabs-panel {
  padding: var(--space-4);
}

/* Pills variant */
.Tabs--pills .Tabs-list {
  border-bottom: none;
  gap: var(--space-1);
}
.Tabs--pills .Tabs-tab {
  border-bottom: none;
  border-radius: var(--radius-md);
  padding: var(--space-1) var(--space-3);
}
.Tabs--pills .Tabs-tab.active {
  background: var(--accent);
  color: white;
}

/* Size variants */
.Tabs--small .Tabs-tab {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
}
.Tabs--large .Tabs-tab {
  padding: var(--space-3) var(--space-5);
  font-size: var(--text-base);
}

/* Full width */
.Tabs--fullWidth .Tabs-list {
  width: 100%;
}
.Tabs--fullWidth .Tabs-tab {
  flex: 1;
  justify-content: center;
}

/* Vertical */
.Tabs--vertical {
  flex-direction: row;
}
.Tabs--vertical .Tabs-list {
  flex-direction: column;
  border-bottom: none;
  border-right: 1px solid var(--bd);
}
.Tabs--vertical .Tabs-tab {
  border-bottom: none;
  border-right: 2px solid transparent;
}
.Tabs--vertical .Tabs-tab.active {
  border-right-color: var(--accent);
}

Accessibility

Keyboard Support

Key Action
Tab Moves focus into/out of the tab list
Arrow Left/Up Moves to the previous tab
Arrow Right/Down Moves to the next tab
Home Moves to the first tab
End Moves to the last tab
Enter/Space Activates the focused tab

Screen Readers

<!-- Basic accessible tabs -->
<div class="Tabs">
    <div class="Tabs-list" role="tablist" aria-label="Account settings">
        <button class="Tabs-tab active" role="tab" 
                id="tab-1" aria-selected="true" 
                aria-controls="panel-1">
            Profile
        </button>
        <button class="Tabs-tab" role="tab" 
                id="tab-2" aria-selected="false" 
                aria-controls="panel-2" tabindex="-1">
            Security
        </button>
    </div>
    <div class="Tabs-panel active" role="tabpanel" 
         id="panel-1" aria-labelledby="tab-1">
        Profile content...
    </div>
    <div class="Tabs-panel" role="tabpanel" 
         id="panel-2" aria-labelledby="tab-2" hidden>
        Security content...
    </div>
</div>

<!-- Icon-only tabs need aria-label -->
<button class="Tabs-tab" role="tab" aria-label="Grid view">
    <i class="ph ph-squares-four"></i>
</button>

<!-- Tabs with counts -->
<button class="Tabs-tab" role="tab">
    Notifications
    <span class="Badge" aria-label="5 unread">5</span>
</button>

Focus Management

<!-- Only the active tab should be in the tab order -->
<div class="Tabs-list" role="tablist">
    <button class="Tabs-tab active" role="tab" 
            aria-selected="true" tabindex="0">Active</button>
    <button class="Tabs-tab" role="tab" 
            aria-selected="false" tabindex="-1">Inactive</button>
    <button class="Tabs-tab" role="tab" 
            aria-selected="false" tabindex="-1">Inactive</button>
</div>

Best Practices

Do

  • Use clear, concise labels — “Settings” not “Configuration Options”
  • Order tabs logically — Most important or commonly used first
  • Limit tab count — 2-7 tabs; more indicates need for different navigation
  • Keep labels short — One or two words per tab
  • Persist tab state — Remember selection on page refresh when appropriate
  • Provide feedback — Clear visual indicator for active tab

Don’t

  • Nest tabs within tabs — Creates confusing navigation hierarchy
  • Use tabs for sequential steps — Use a stepper or wizard instead
  • Hide critical content — Essential info should be visible by default
  • Mix tab styles — Be consistent within a page or section
  • Use tabs for unrelated content — Tabs should group related views
  • Forget mobile — Consider if tabs make sense on smaller screens