Sliding Tabs

Tab navigation with a sliding pill indicator built on Radix Tabs, with default and glass variants and a smooth measured animation.

v0.1.0
🍰
🏃
🎓
Intermediate

Sliding Tabs

Default

Muted track with a primary-colored sliding pill. Hover a tab to preview the indicator before selecting.

Manage your profile, email, and connected accounts.

Glass

Frosted glass variant for dark surfaces. Supports optional icons per tab.

Record a voice note and let the app transcribe it.

Install

Requires a project with shadcn/ui initialized. Installs all 2 files and any listed registry dependencies.

npx shadcn@latest add https://manglekuo.com/showcase/r/sliding-tabs.json
  • @ui/sliding-tabs.tsx
  • @ui/sliding-tabs.css

npm deps: @radix-ui/react-tabs, class-variance-authority

Documentation

Sliding Tabs

Tab navigation with a sliding pill indicator built on Radix Tabs. Includes default and glass variants, plus convenience helpers for item-driven bars and panels.

How it works

A small hook measures the active (and hovered) trigger's geometry with getBoundingClientRect and writes it to CSS custom properties on the list. Two indicator elements animate transform, width, and height toward those values. Because the animated properties' computed values actually change, the transition fires reliably no matter how fast or in what order you switch tabs — a MutationObserver tracks Radix's data-state changes and a ResizeObserver keeps it aligned on layout changes.

Usage

Composable API

import {  SlidingTabs,  SlidingTabsList,  SlidingTabsTrigger,  SlidingTabsContent,} from '@/components/ui/sliding-tabs'; <SlidingTabs defaultValue="account">  <SlidingTabsList>    <SlidingTabsTrigger value="account">Account</SlidingTabsTrigger>    <SlidingTabsTrigger value="password">Password</SlidingTabsTrigger>  </SlidingTabsList>  <SlidingTabsContent value="account">Account panel</SlidingTabsContent>  <SlidingTabsContent value="password">Password panel</SlidingTabsContent></SlidingTabs>Language: jsx

Item-driven helpers

import {  SlidingTabs,  SlidingTabsBar,  SlidingTabsContent,  SlidingTabsPanel,} from '@/components/ui/sliding-tabs'; const tabs = [  { value: 'voice', label: 'Voice', icon: Mic },  { value: 'type', label: 'Type', icon: Keyboard },]; <SlidingTabs defaultValue="voice">  <SlidingTabsBar items={tabs} variant="glass" aria-label="Input method" />  {tabs.map(({ value }) => (    <SlidingTabsContent key={value} value={value}>      <SlidingTabsPanel>Panel content</SlidingTabsPanel>    </SlidingTabsContent>  ))}</SlidingTabs>Language: jsx

Props

SlidingTabsBar

  • items (SlidingTabItem[]): Tab definitions with value, label, optional icon, and disabled.
  • variant ("default" | "glass"): Visual style of the tab bar.
  • listClassName / triggerClassName: Extra classes for the list or triggers.
  • disabled: Disables all tabs when true.
  • aria-label: Accessible label for the tab list.

Variants

  • default: Muted rounded track with a primary-colored pill. Best on standard surfaces.
  • glass: Frosted glass pill on dark or image backgrounds. Uses backdrop-blur and translucent borders.

CSS

The component ships sliding-tabs.css with the .sliding-pill utilities. It is imported automatically from sliding-tabs.tsx, so no extra setup is required.