How to Animate Tabs and Segmented Controls with a Sliding Indicator
Step-by-step guide to building a sliding active pill for tabs and segmented controls: measuring DOM rects, layout animations, spring transitions, and tabpanel swapping.
Core Takeaway
A premium segmented control uses a single sliding indicator pill that animates between active items. Calculate the target tab's offsetLeft and offsetWidth, and animate the pill's transform: translateX() and width using a spring or ease-out curve. Accompany this with a subtle opacity and slide transition for the corresponding tabpanel content.
Single sliding pill vs individual borders
Switching active tabs by toggling border-bottom on each button looks abrupt. A unified indicator element that glides smoothly beneath the selected label reinforces spatial relationship and feels cohesive.
Math of the sliding pill
To avoid layout thrashing, measure the relative position of the target tab using its offsetLeft and offsetWidth. Animate the pill container using transform: translateX(x) and transform: scaleX(scale) or width.
- Position the indicator pill absolutely inside the tablist container
- Update coordinates whenever the active index changes or the viewport resizes
- Keep label text above the indicator with z-index: 1
- Change inactive label text color with a smooth 150ms crossfade
Tabpanel content transitions
When switching tabs, avoid heavy 3D flips or long slides. A quick 150ms fade coupled with a 10px directional slide (sliding in from the right if moving forward, left if moving backward) clarifies progression.
Keyboard navigation guidelines
Support standard W3C tabs behavior: ArrowLeft and ArrowRight navigate between tabs, and Home/End jump to the start and end. Focus must remain visible on the active tab.
Production Implementation Snippet
/* Sliding pill container */
.tab-list {
position: relative;
display: flex;
background: #f3f4f6;
padding: 4px;
border-radius: 12px;
}
.sliding-indicator {
position: absolute;
top: 4px;
bottom: 4px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1),
width 200ms cubic-bezier(0.16, 1, 0.3, 1);
}Key Principles to Remember
- Use an absolutely positioned indicator that slides across the active tab coordinate
- Keep tab change animations fast (under 200ms)
- Accompany with directional tabpanel entry motion
- Implement full arrow-key keyboard navigation
Frequently Asked Questions
Sources
- Tabs Pattern — W3C WAI-ARIA Authoring Practices
- Segmented Buttons — Material Design 3