Skip to content

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.

More accordions & tabs components

← Browse all NudaUI components