Navigation✨ Premium

Tab Slider

Tab rail with sliding indicator — positioned via getBoundingClientRect, animated by a cubic-bezier transition, adapts to any tab width.

JSSlider

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

Main app navigation — SaaS Dashboard — Tab Slider example 1

① Main app navigation — SaaS Dashboard

WhenA web app with 3-4 distinct sections (dashboard, analytics, reports) needs a lightweight horizontal navigation bar in the page header.
WhyThe tab rail is the most readable pattern for this UI; the sliding indicator marks the active section without a page reload and without bloating the header.
Settings3 tabs (Dashboard / Analytics / Reports), transition .35s, brand gradient (#6366f1 → #8b5cf6), --bg-card: #1c1c2e.
Billing period selector — Monthly / Annual / Team — Tab Slider example 2

② Billing period selector — Monthly / Annual / Team

WhenA pricing page offers 2-3 billing periods or plan tiers; plain toggle buttons lack the fluidity this high-stakes page deserves.
WhyThe indicator sliding toward 'Annual' creates a natural sense of forward movement and reinforces the premium feel of the page before prices even update.
Settings2-3 short tabs, --radius-lg: 100px; --radius-md: 100px for a pill look, --bg-card: rgba(18,18,30,0.9).
Category filter bar — Blog or media library — Tab Slider example 3

③ Category filter bar — Blog or media library

WhenA blog, gallery, or effects library filters its content by category horizontally with 4-6 categories max.
WhyThe sliding indicator gives immediate visual feedback on the active category, more dynamic than a plain static underline, without a page reload.
Settings4-5 tabs (All / Design / Dev / Marketing), --radius-lg: 0.5rem; --radius-md: 0.25rem for a flatter style.

How it works

On click, the JS reads the exact rectangle of the clicked <span class="tab-slider-item"> via getBoundingClientRect() and subtracts the container's rectangle to get a relative left offset in pixels. It then sets indicator.style.width and indicator.style.left simultaneously: the indicator takes the exact width and position of the clicked tab, regardless of label length.

A CSS transition 0.35s cubic-bezier(0.4, 0, 0.2, 1) (Material Design standard-ease) is applied to the indicator <div>. Because width and left change at the same time, the indicator shrinks or stretches en route when tabs have different widths — producing a natural glide rather than a plain rigid translation.

The indicator is position: absolute inside the rail, anchored at top: 6px and height: calc(100% - 12px) for a 6 px inset. It carries a background: linear-gradient(135deg, #6366f1 → #8b5cf6) and an indigo box-shadow glow. The <span> tabs sit in front via z-index: 1.

There is no prefers-reduced-motion handling in the delivered code: the transition always runs. To support motion-sensitive users, add @media (prefers-reduced-motion: reduce) { .tab-slider-indicator { transition: none } }.

Accessibility

  • No prefers-reduced-motion handling: the indicator transition runs at all times. Add @media (prefers-reduced-motion: reduce) { .tab-slider-indicator { transition: none } } manually to disable it.
  • Tabs are <span> elements, not natively keyboard-focusable. For keyboard navigation, convert them to <button> or add tabindex="0" with a keydown handler for Enter and Space.
  • No ARIA roles are declared: the container is not announced as a tablist, items carry neither role="tab" nor aria-selected. The full WAI-ARIA Tabs pattern requires these attributes.
  • Active tab contrast (white on indigo #6366f1): ratio ≈ 4.1:1 — below the WCAG AA threshold of 4.5:1 for normal text. Use a darker indigo or font size ≥ 18 px to meet AA for large text.
  • Inactive tab contrast (#a1a1aa on #12121a): ratio ≈ 7.6:1 — WCAG AA and AAA met.

Browser compatibility

Requires only getBoundingClientRect, classList, and querySelectorAll — DOM APIs available in all modern browsers and IE11.

Chrome 60+✓ Full
Firefox 55+✓ Full
Safari 12+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Touch OK
Android Chrome✓ Full

Without JavaScript, the indicator stays at its initial position (set by the inline <code>style</code> attribute) and tabs render without interaction — no fatal error, the layout stays readable.

The code

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

index.html — structure
<div class="tab-slider-container" id="tabSlider">
  <div class="tab-slider-indicator" id="tabSliderIndicator"></div>
  <span class="tab-slider-item active" data-index="0">Tab 1</span>
  <span class="tab-slider-item" data-index="1">Tab 2</span>
  <span class="tab-slider-item" data-index="2">Tab 3</span>
</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
--bg-card #12121a Background color of the tab rail. Set to transparent or a brand color to integrate the component over any background.
--border rgba(255,255,255,0.08) Color and opacity of the container border. Increase opacity for a more visible edge; transparent to remove the outline.
--radius-lg 0.75rem Rail corner radius. 100px = pill look; 0 = sharp corners.
--radius-md 0.5rem Indicator corner radius. Keep slightly smaller than --radius-lg to preserve the visual inset.
--text-secondary #a1a1aa Inactive tab color. #64748b suits a light background; #6b7280 suits a mid-tone background.
Indicator gradient linear-gradient(135deg, #6366f1 0, #8b5cf6 100%) Edit the two hex colors in .tab-slider-indicator { background: … } to match your brand. The adjacent box-shadow carries the same indigo tint.
Indicator transition .35s cubic-bezier(.4,0,.2,1) In .tab-slider-indicator { transition: … }: shorten to .2s ease for snappier response, or 0s to disable animation (reduced-motion mode).

FAQ

Yes, pure vanilla: one indicator <div>, <span> tabs, and roughly 15 lines of JS. Zero npm dependencies, no CSS framework needed. Drop the HTML into Angular, React, or Vue by wrapping it in a component and calling the init after mount.
Add or remove <span class="tab-slider-item"> elements inside the container. The initial call r(o[0]) automatically positions the indicator on the first tab at load. No index to configure — each tab's width is measured on the fly via getBoundingClientRect.
No: the getBoundingClientRect calculation only runs on click. For fluid layouts where tab widths change on resize, add window.addEventListener('resize', () => r(activeTab)) (keeping a reference to the current active tab element) to recalculate the indicator position.