Tabs
Installation
Tabs use Standard’s base styles. Include the stylesheet:
<link rel="stylesheet" href="https://standard.operator.onl/styles/standard-core.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
<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><button class="Button">Click me</button></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.
<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><!DOCTYPE html>
<html lang="en">
<head>
<title>My App</title>
</head>
</html></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(--bd);
border-radius: var(--r-m);
padding: var(--space-1);
background: var(--bg-s);
}
.Tabs--bordered .Tabs-tab.active {
background: var(--bg);
border-radius: var(--r-s);
box-shadow: var(--sh-s);
}
/* 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(--fs-sm);
font-weight: 500;
cursor: pointer;
transition: color var(--dur-f), border-color var(--dur-f);
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(--r-m);
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(--r-m);
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(--fs-xs);
}
.Tabs--large .Tabs-tab {
padding: var(--space-3) var(--space-5);
font-size: var(--fs-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