Navigation✨ Premium

Tabs Animated

A gradient pill indicator slides between tabs via translateX — click selection, panels fade in over 0.3 s, zero external dependencies.

CSSJSTransition

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Navigation has 36 effects, including 4 free. Explore the category →

3 usage examples

Mobile app — Main navigation between sections — Tabs Animated example 1

① Mobile app — Main navigation between sections

WhenThe user navigates between the main sections of an app (dashboard, messaging, profile) via a tab bar at the top of the screen.
WhyThe sliding indicator unambiguously signals the active section and gives instant feedback on click — suited to fast mobile navigation patterns.
SettingsTransition at 0.2 s for snappier response, brand colors replacing the indigo gradient, max-width: 100% for a full-width bar.
Technical documentation — Code examples by language — Tabs Animated example 2

② Technical documentation — Code examples by language

WhenAn API doc page offers the same example in JavaScript, Python, and PHP — the user picks their language via the tabs.
WhyThe animated indicator confirms the code block switch without a page reload. Equal-width tabs read better than underlined links in a technical reading context.
SettingsGradient replaced by a solid color (background: #1e293b) for an editorial look, panels with font-family: monospace and background: rgba(0,0,0,.4).
Analytics dashboard — Period filter (Day / Week / Month) — Tabs Animated example 3

③ Analytics dashboard — Period filter (Day / Week / Month)

WhenThe header of a stats widget offers 3 time granularities — the user switches the displayed period without leaving the page.
WhyThe sliding indicator is more compact and legible than a <code>&lt;select&gt;</code> or radio buttons for 3 fixed options; it integrates cleanly into a card header.
SettingsTransition at 0.15 s for immediate data response, max-width: 280px to fit in a compact widget, indigo gradient kept for data accent.

How it works

The sliding indicator is a <div class="tab-indicator"> positioned absolute inside the position: relative header. Its width is calc(33.333% - 2.67px) — exactly one third of the container, since buttons are flex: 1 1 0%. It carries a gradient linear-gradient(135deg, #6366f1, #8b5cf6) and a border-radius: 8px.

Movement is driven by a single JS statement: indicator.style.transform = 'translateX(' + 100 * index + '%)'. The value 100% equals 100% of the indicator's own width — which is exactly one tab's width. The browser interpolates the motion via transition: transform .3s cubic-bezier(.4, 0, .2, 1) (Material Design standard ease), with no absolute coordinate calculations.

On tab change, classList.toggle('active', bool) marks the matching button and panel active and unchecks the others. The active panel switches from display: none to display: block and triggers the CSS fadeIn animation (opacity 0→1, translateY 10→0 px, 0.3 s duration). Inactive buttons transition from rgba(255,255,255,.6) to #fff via transition: color .3s.

Buttons at flex: 1 1 0% all occupy strictly the same width regardless of label length — the indicator always lands precisely under the right tab without any extra calculation. The switchTab(n) function is exposed in the global scope and can be called programmatically from any external script.

Accessibility

  • prefers-reduced-motion not implemented — the code contains no prefers-reduced-motion media query: the indicator transition and fadeIn run even when the OS requests reduced motion. Add manually: @media (prefers-reduced-motion: reduce) { .tab-indicator, .tab-panel { transition: none; animation: none; } }.
  • Tabs are native <button> elements — keyboard-focusable (Tab / Shift+Tab) and announced by screen readers without extra configuration.
  • No ARIA Tabs pattern roles (role="tablist", role="tab", aria-selected, role="tabpanel", aria-controls) — add them if WCAG 1.3.1 compliance is required.
  • Inactive panels are hidden via display: none — removed from the accessibility tree natively, without explicit aria-hidden.
  • Active text contrast: #fff on the indigo gradient (#6366f1) — ratio > 4.5:1, WCAG AA compliant.

Browser compatibility

CSS transition, transform, flexbox, and linear-gradient — supported in all modern browsers. Zero external dependencies.

Chrome 36+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 18+✓ Full
Mobile iOS✓ Touch OK
Android Chrome✓ Full

Without JS, tabs remain visible but the indicator does not slide and panels do not switch — the first tab panel stays displayed statically. Without <code>flexbox</code> support, buttons stack vertically but remain functional.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="tabs-container">
  <div class="tabs-header">
    <div class="tab-indicator" id="tabIndicator"></div>
    <button class="tab-btn active" onclick="switchTab(0)">Tab 1</button>
    <button class="tab-btn" onclick="switchTab(1)">Tab 2</button>
    <button class="tab-btn" onclick="switchTab(2)">Tab 3</button>
  </div>
  <div class="tabs-content">
    <div class="tab-panel active" data-tab="0">Tab 1 content</div>
    <div class="tab-panel" data-tab="1">Tab 2 content</div>
    <div class="tab-panel" data-tab="2">Tab 3 content</div>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
--primary (CSS :root) #6366f1 Accent color used by .tab-btn.active { color } — set on :root or directly on .tabs-container for a narrower scope.
.tab-indicator background linear-gradient(135deg, #6366f1, #8b5cf6) Indicator pill gradient — replace with a solid color (#3b82f6) or your brand palette.
.tab-indicator transition transform .3s cubic-bezier(.4,0,.2,1) Slide duration and easing — .15s ease for an instant data response, .5s ease-in-out for a softer editorial feel.
.tabs-container max-width 350px Maximum block width — remove or set to 100% for a full-width bar in a fluid layout.
.tab-btn color (inactive) rgba(255,255,255,.6) Opacity of inactive tab text — raise to rgba(255,255,255,.85) for better readability on light backgrounds.
.tab-panel animation fadeIn .3s ease Content animation on tab switch — shorten to fadeIn .1s ease for near-instant display, or remove animation for a plain swap.
switchTab(n) — global JS Programmatic tab switch without user click — switchTab(2) activates the 3rd tab. Trigger from a timer, a business event, or an external link.

FAQ

Yes, but two values need updating. In the CSS, replace width: calc(33.333% - 2.67px) with calc(100% / N - gap) (e.g. calc(25% - 3px) for 4 tabs). The JS offset stays translateX(100 * index %) — nothing else needs to change on the logic side.
No. Buttons are set to flex: 1 1 0%: they share the container width in strictly equal portions, regardless of text. The indicator inherits this fixed width and always lands exactly under the right tab.
Copy the HTML, CSS, and JS into a component. In React, replace inline onclick="switchTab(n)" with onClick={() => switchTab(n)} and trigger switchTab(defaultTab) inside useEffect(() => {}, []) on the client side. In Vue, use @click="switchTab(n)" and mounted() { switchTab(0); }. The effect does not touch the DOM outside its own container.