Features
- No dependencies
- Three styles out of the box: underline, classic tabs, and a sliding segmented control
- Animated indicator that slides between tabs with an elastic stretch, plus a spring tease on hover
- Content panels cross-fade and the container resizes between tabs
- Too many tabs? They scroll horizontally — drag, wheel, or swipe, with momentum and edge fades
- Opt into wrapping onto multiple rows instead of scrolling
- Swipe left/right on the content to change tabs on touch
- Interruptible: click another tab mid-animation and the indicator redirects, no waiting
- Detached tabs: the bar and its panels don't have to be siblings
- Fires a
tab-changedevent, and works with a plain.click() - Themeable with a handful of CSS variables
Basic Usage
A .tab-bar of .tab-bar-buttons, with a matching .tab-contents next to it. Buttons and panels pair up by data-tab; mark the starting pair with active:
Line two.
Line three.
<div class="tab-bar"> <button class="tab-bar-button active" data-tab="overview">Overview</button> <button class="tab-bar-button" data-tab="details">Details</button> <button class="tab-bar-button" data-tab="reviews">Reviews</button> </div> <div class="tab-contents"> <div class="tab-content active" data-tab="overview">…</div> <div class="tab-content" data-tab="details">…</div> <div class="tab-content" data-tab="reviews">…</div> </div>
Styles
Add a tab-style-* class to the bar and its panels. Without one you get the underline style. Classic tabs connect into a filled panel; slide is a pill-shaped segmented control where the spring/tease is most visible.
<div class="tab-bar tab-style-tabs">…</div> <div class="tab-contents tab-style-tabs">…</div> <div class="tab-bar tab-style-slide">…</div> <div class="tab-contents tab-style-slide">…</div>
Detached panels
The bar and its panels don't have to be siblings. Give the bar an id and point the contents at it with data-tab-bar:
— anything can sit between the bar and its panels —
<div class="tab-bar" id="account">…</div> <p>…anything in between…</p> <div class="tab-contents" data-tab-bar="account">…</div>
Scrolling & wrapping
When there are more tabs than fit, the bar scrolls horizontally by default — drag it, wheel over it, or swipe the content. A soft fade marks whichever edge has hidden tabs, and selecting a tab scrolls it into view.
Prefer they wrap onto multiple rows instead? Add data-tab-wrap. The indicator follows the active tab across rows.
<!-- scrolls (default) --> <div class="tab-bar">…</div> <!-- wraps to multiple rows --> <div class="tab-bar" data-tab-wrap>…</div>
Disable swipe
On touch, swiping the content steps between tabs. Add data-tab-no-swipe to turn that off — handy when a panel holds its own swipeable carousel. The buttons still work.
<div class="tab-bar" data-tab-no-swipe>…</div>
Events
The bar dispatches a tab-changed event whenever the active tab changes. event.detail is the new tab's data-tab:
tab-changed: (waiting)
bar.addEventListener("tab-changed", e => {
console.log("switched to", e.detail)
})
Programmatic & dynamic
Switch tabs by calling .click() on a button — the library listens for normal clicks. Bars added after load are picked up with FluidTabs.init() (idempotent; pass a root element to scan just a subtree):
// jump to a tab
bar.querySelector('[data-tab="reviews"]').click()
// initialise bars added after load
FluidTabs.init() // whole document
FluidTabs.init(sectionEl) // within an element
Theming
Tweak the feel with a handful of CSS variables, globally or per-bar: --tab-transition-duration, --tab-transition-easing, --tab-slide-lag (the elastic stretch), --tab-tease-x (hover tease), and --tab-fade (scroll edge fade). This bar is slowed down with an exaggerated spring:
.tab-bar {
--tab-transition-duration: .55s;
--tab-tease-x: 24px;
--tab-slide-lag: .6;
}