tabs glass oklch backdrop-filter keyboard accessible interactive color tabs glass oklch backdrop-filter keyboard aria accessible frosted glass tab navigation OKLCH color tab bar accessible tab component with keyboard navigation frosted glass tabs
Glass OKLCH Tabs
Fetch pattern JSON:
curl https://webspire.de/patterns/tabs/glass-oklch.json glass-oklch.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glass OKLCH Tabs — Webspire Pattern</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root {
--tab-hue: 260;
--tab-sat: 0.12;
}
.glass-tab-list {
display: flex;
gap: 4px;
padding: 6px;
border-radius: 16px;
background: oklch(92% 0.02 var(--tab-hue) / 0.15);
backdrop-filter: blur(12px);
border: 1px solid oklch(80% 0.03 var(--tab-hue) / 0.2);
}
.glass-tab {
flex: 1;
padding: 10px 20px;
border-radius: 10px;
border: 1px solid transparent;
background: transparent;
color: oklch(45% 0.05 var(--tab-hue));
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background 180ms, color 180ms, border-color 180ms;
white-space: nowrap;
}
.glass-tab:hover:not([aria-selected="true"]) {
background: oklch(92% 0.03 var(--tab-hue) / 0.3);
color: oklch(30% 0.06 var(--tab-hue));
}
.glass-tab[aria-selected="true"] {
background: oklch(92% 0.03 var(--tab-hue) / 0.4);
backdrop-filter: blur(8px);
border-color: oklch(80% 0.05 var(--tab-hue) / 0.3);
color: oklch(25% 0.1 var(--tab-hue));
font-weight: 600;
}
.glass-tab:focus-visible {
outline: 2px solid oklch(60% 0.2 var(--tab-hue));
outline-offset: 2px;
}
.glass-tab-panel[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
.glass-tab { transition: none; }
}
/* Dark mode overrides */
@media (prefers-color-scheme: dark) {
.glass-tab-list {
background: oklch(25% 0.03 var(--tab-hue) / 0.6);
border-color: oklch(40% 0.05 var(--tab-hue) / 0.3);
}
.glass-tab {
color: oklch(75% 0.06 var(--tab-hue));
}
.glass-tab:hover:not([aria-selected="true"]) {
background: oklch(30% 0.04 var(--tab-hue) / 0.5);
color: oklch(88% 0.05 var(--tab-hue));
}
.glass-tab[aria-selected="true"] {
background: oklch(35% 0.06 var(--tab-hue) / 0.7);
border-color: oklch(50% 0.08 var(--tab-hue) / 0.4);
color: oklch(95% 0.05 var(--tab-hue));
}
}
</style>
</head>
<body class="bg-gradient-to-br from-indigo-50 via-white to-violet-50 min-h-screen font-sans antialiased p-8
dark:from-slate-950 dark:via-slate-900 dark:to-indigo-950">
<div class="mx-auto max-w-3xl">
<div class="mb-10 text-center">
<h1 class="text-3xl font-bold text-slate-900 dark:text-slate-100">Glass OKLCH Tabs</h1>
<p class="mt-2 text-slate-500 dark:text-slate-400">
Tab hue is controlled by <code class="rounded bg-slate-100 px-1.5 py-0.5 text-xs font-mono dark:bg-slate-800">--tab-hue</code>.
</p>
</div>
<!-- Hue picker demo -->
<div class="mb-8 flex items-center gap-4">
<label for="hue-range" class="text-sm font-medium text-slate-600 dark:text-slate-400 shrink-0">Tab hue</label>
<input id="hue-range" type="range" min="0" max="360" value="260"
class="flex-1 accent-indigo-500"
oninput="document.documentElement.style.setProperty('--tab-hue', this.value)">
<span id="hue-val" class="w-10 text-right text-sm font-mono text-slate-500 dark:text-slate-400">260</span>
</div>
<!-- Tab component -->
<div role="tablist" aria-label="Product features" class="glass-tab-list" id="tab-list">
<button role="tab" id="tab-overview" aria-controls="panel-overview" aria-selected="true" tabindex="0" class="glass-tab">
Overview
</button>
<button role="tab" id="tab-analytics" aria-controls="panel-analytics" aria-selected="false" tabindex="-1" class="glass-tab">
Analytics
</button>
<button role="tab" id="tab-integrations" aria-controls="panel-integrations" aria-selected="false" tabindex="-1" class="glass-tab">
Integrations
</button>
<button role="tab" id="tab-settings" aria-controls="panel-settings" aria-selected="false" tabindex="-1" class="glass-tab">
Settings
</button>
</div>
<!-- Tab panels -->
<div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" class="glass-tab-panel mt-6">
<div class="rounded-2xl border border-slate-200/60 bg-white/70 p-8 backdrop-blur-sm dark:border-slate-700/40 dark:bg-slate-800/60">
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Overview</h2>
<p class="mt-3 text-slate-600 dark:text-slate-300">Your workspace at a glance. See recent activity, pending tasks, and key metrics in one view.</p>
<div class="mt-6 grid grid-cols-3 gap-4">
<div class="rounded-xl bg-indigo-50 p-4 dark:bg-indigo-950/40">
<p class="text-2xl font-bold text-indigo-600 dark:text-indigo-400">142</p>
<p class="text-xs text-slate-500 mt-1">Active projects</p>
</div>
<div class="rounded-xl bg-violet-50 p-4 dark:bg-violet-950/40">
<p class="text-2xl font-bold text-violet-600 dark:text-violet-400">98%</p>
<p class="text-xs text-slate-500 mt-1">Uptime SLA</p>
</div>
<div class="rounded-xl bg-emerald-50 p-4 dark:bg-emerald-950/40">
<p class="text-2xl font-bold text-emerald-600 dark:text-emerald-400">2.1s</p>
<p class="text-xs text-slate-500 mt-1">Avg response</p>
</div>
</div>
</div>
</div>
<div id="panel-analytics" role="tabpanel" aria-labelledby="tab-analytics" class="glass-tab-panel mt-6" hidden>
<div class="rounded-2xl border border-slate-200/60 bg-white/70 p-8 backdrop-blur-sm dark:border-slate-700/40 dark:bg-slate-800/60">
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Analytics</h2>
<p class="mt-3 text-slate-600 dark:text-slate-300">Deep-dive into usage trends, conversion funnels, and retention metrics.</p>
<div class="mt-6 h-32 rounded-xl bg-gradient-to-r from-violet-100 to-indigo-100 dark:from-violet-950/40 dark:to-indigo-950/40 flex items-center justify-center">
<p class="text-sm text-slate-400">Chart placeholder</p>
</div>
</div>
</div>
<div id="panel-integrations" role="tabpanel" aria-labelledby="tab-integrations" class="glass-tab-panel mt-6" hidden>
<div class="rounded-2xl border border-slate-200/60 bg-white/70 p-8 backdrop-blur-sm dark:border-slate-700/40 dark:bg-slate-800/60">
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Integrations</h2>
<p class="mt-3 text-slate-600 dark:text-slate-300">Connect your favourite tools. Sync data in real time without writing a single line of code.</p>
<ul class="mt-6 space-y-3 text-sm text-slate-600 dark:text-slate-300">
<li class="flex items-center gap-3"><span class="h-2 w-2 rounded-full bg-emerald-400"></span>Slack — Connected</li>
<li class="flex items-center gap-3"><span class="h-2 w-2 rounded-full bg-emerald-400"></span>GitHub — Connected</li>
<li class="flex items-center gap-3"><span class="h-2 w-2 rounded-full bg-slate-300 dark:bg-slate-600"></span>Jira — Not connected</li>
</ul>
</div>
</div>
<div id="panel-settings" role="tabpanel" aria-labelledby="tab-settings" class="glass-tab-panel mt-6" hidden>
<div class="rounded-2xl border border-slate-200/60 bg-white/70 p-8 backdrop-blur-sm dark:border-slate-700/40 dark:bg-slate-800/60">
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Settings</h2>
<p class="mt-3 text-slate-600 dark:text-slate-300">Manage your account, team members, billing, and notification preferences.</p>
<div class="mt-6 space-y-4">
<label class="flex items-center gap-3 text-sm text-slate-700 dark:text-slate-300">
<input type="checkbox" checked class="rounded accent-indigo-500">
Email notifications
</label>
<label class="flex items-center gap-3 text-sm text-slate-700 dark:text-slate-300">
<input type="checkbox" class="rounded accent-indigo-500">
Slack digest
</label>
</div>
</div>
</div>
</div>
<script>
// Hue range display
const hueRange = document.getElementById('hue-range');
const hueVal = document.getElementById('hue-val');
hueRange.addEventListener('input', () => { hueVal.textContent = hueRange.value; });
// Tab keyboard + click switching
const tabs = Array.from(document.querySelectorAll('[role="tab"]'));
function selectTab(tab) {
tabs.forEach(t => {
t.setAttribute('aria-selected', 'false');
t.setAttribute('tabindex', '-1');
document.getElementById(t.getAttribute('aria-controls')).hidden = true;
});
tab.setAttribute('aria-selected', 'true');
tab.setAttribute('tabindex', '0');
document.getElementById(tab.getAttribute('aria-controls')).hidden = false;
tab.focus();
}
tabs.forEach(tab => {
tab.addEventListener('click', () => selectTab(tab));
tab.addEventListener('keydown', e => {
const idx = tabs.indexOf(e.currentTarget);
if (e.key === 'ArrowRight') { e.preventDefault(); selectTab(tabs[(idx + 1) % tabs.length]); }
if (e.key === 'ArrowLeft') { e.preventDefault(); selectTab(tabs[(idx - 1 + tabs.length) % tabs.length]); }
if (e.key === 'Home') { e.preventDefault(); selectTab(tabs[0]); }
if (e.key === 'End') { e.preventDefault(); selectTab(tabs[tabs.length - 1]); }
});
});
</script>
</body>
</html>
Details
Responsive Dark Mode SSR Safe Copy & Paste
Experimental Draft
tabsglassoklchbackdrop-filterkeyboardaccessibleinteractivecolor
Slots
| Name | Required | Description |
|---|---|---|
| tabs | Yes | Tab buttons with role="tab", aria-selected, aria-controls. |
| panels | Yes | Tab panels with role="tabpanel" and aria-labelledby. |
Props
| Name | Type | Default | Description |
|---|---|---|---|
| --tab-hue | number | 260 | OKLCH hue angle (0–360) for the tab glass color. |
| --tab-sat | number | 0.12 | OKLCH chroma factor for the tab glass tint. |
Glass-style tab bar using oklch() colors with a --tab-hue custom property so the entire color scheme shifts with one value. The tab list has backdrop-filter: blur(12px) and a semi-transparent OKLCH background. Active tabs get a lighter frosted fill with a subtle border. Dark mode overrides are scoped inside @media (prefers-color-scheme: dark). Keyboard navigation implements the ARIA tab pattern: Arrow keys cycle through tabs, Home/End jump to extremes, and tabindex management follows roving focus.