Animated Tabs
A copy-paste accordions & tabs component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.
Accordions & TabsHTMLCSSJavaScriptany framework
Copy into your project
HTML
<div class="nuda-tabs">
<div class="nuda-tabs__bar" role="tablist">
<button class="nuda-tabs__tab is-active" role="tab" aria-selected="true" data-tab="1">Tab 1</button>
<button class="nuda-tabs__tab" role="tab" aria-selected="false" data-tab="2">Tab 2</button>
<button class="nuda-tabs__tab" role="tab" aria-selected="false" data-tab="3">Tab 3</button>
<div class="nuda-tabs__indicator"></div>
</div>
<div class="nuda-tabs__panel is-active" data-panel="1" role="tabpanel">Panel 1 content</div>
<div class="nuda-tabs__panel" data-panel="2" role="tabpanel">Panel 2 content</div>
<div class="nuda-tabs__panel" data-panel="3" role="tabpanel">Panel 3 content</div>
</div>CSS
/* Animated Tabs
Tabs with a sliding indicator pill.
Customize: --tab-accent, --tab-bg */
.nuda-tabs {
--tab-accent: #e4ff54;
--tab-bg: rgba(255, 255, 255, 0.03);
max-width: 500px;
}
.nuda-tabs__bar {
display: flex;
position: relative;
background: var(--tab-bg);
border-radius: 10px;
padding: 4px;
gap: 2px;
}
.nuda-tabs__tab {
flex: 1;
text-align: center;
font-size: 0.85rem;
padding: 0.5rem 0.75rem;
color: #888;
background: none;
border: none;
border-radius: 8px;
cursor: pointer;
position: relative;
z-index: 1;
transition: color 0.2s;
}
.nuda-tabs__tab.is-active {
color: #0a0a0a;
font-weight: 600;
}
.nuda-tabs__indicator {
position: absolute;
top: 4px;
bottom: 4px;
left: 4px;
width: calc(33.33% - 4px);
background: var(--tab-accent);
border-radius: 8px;
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 0;
}
.nuda-tabs__panel {
display: none;
padding: 1rem;
margin-top: 0.5rem;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 10px;
color: #ccc;
font-size: 0.85rem;
animation: nuda-tab-fade 0.3s ease-out;
}
.nuda-tabs__panel.is-active { display: block; }
@keyframes nuda-tab-fade {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.nuda-tabs__indicator { transition: none; }
.nuda-tabs__panel { animation: none; }
}JavaScript
/* Animated Tabs — Slide indicator and switch panels */
(function () {
var containers = document.querySelectorAll('.nuda-tabs');
containers.forEach(function (tabs) {
var tabBtns = tabs.querySelectorAll('.nuda-tabs__tab');
var panels = tabs.querySelectorAll('.nuda-tabs__panel');
var indicator = tabs.querySelector('.nuda-tabs__indicator');
if (!tabBtns.length || !indicator) return;
tabBtns.forEach(function (btn, i) {
btn.addEventListener('click', function () {
tabBtns.forEach(function (b) {
b.classList.remove('is-active');
b.setAttribute('aria-selected', 'false');
});
panels.forEach(function (p) { p.classList.remove('is-active'); });
btn.classList.add('is-active');
btn.setAttribute('aria-selected', 'true');
var panel = tabs.querySelector('[data-panel="' + btn.dataset.tab + '"]');
if (panel) panel.classList.add('is-active');
indicator.style.transform = 'translateX(' + (i * 100) + '%)';
});
});
});
})();How to use Animated Tabs
Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.
Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.