Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Mobile

Swipeable Tabs

Mobile

Swipeable Tabs React component for Tailwind CSS v4

Swipe DeckCart drawer
Live props
Behavior
defaultValue
swipe
swipeWithMouse
lazy
keepMounted
scrollToTopOnReselect
activationMode
haptics
Appearance
variant
size
tone
color
radius
Layout
fullWidth
layout
Content
aria-label
Code
import { SwipeableTabs } from "@/components/ui/swipeable-tabs"

<SwipeableTabs />
  1. Home
  2. Components
  3. Mobile
  4. Swipeable Tabs
Pro

Swipeable Tabs React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

React swipeable tabs whose pages sit side by side and follow your thumb: swipe and the indicator tracks the finger, tap and the pages slide.

Swipe the content sideways with a finger: the pages move with native momentum, one page per swipe, and the underline, pill or segmented thumb stretches between tabs in step with the pages; the label color flips half way and a light selection tick plays when the swipe settles on a new tab (Android, iOS 17.4+). Vertical scrolling inside a page never changes the tab, and a horizontal rail inside a page scrolls first. Tap a tab and the pages glide to it; tap the active tab again to scroll its page back to the top. On a trackpad, swipe horizontally with two fingers; with swipeWithMouse, drag the pages with the mouse (past a quarter of the width or a quick flick moves one page, a short drag springs back, and the click after a drag never reaches a row). With the keyboard, Tab reaches the selected tab, Left / Right move between tabs (mirrored in RTL, disabled tabs skipped, wrapping at the ends), Home / End jump, and in manual mode Enter or Space selects the focused tab; Tab again moves into the page, and when a swipe carries focus off screen it moves to the new tab instead of getting lost. When the tabs overflow, the row scrolls sideways with edge fades and keeps the active tab in view. Under reduced motion taps jump instead of sliding.

Swipeable Tabs is a Pro React component in the React mobile components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add swipeable-tabs, together with Badge, useControllableState, useScrollEdges, Haptics, Motion presets and 2 more and the npm package motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add swipeable-tabs

Swipeable Tabs is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import SwipeableTabs from @/components/ui/swipeable-tabs.

import { useState } from "react"
import { SwipeableTabs } from "@/components/ui/swipeable-tabs"

const CLIENTS = [
  { name: "Tech Solutions Inc.", city: "New York", status: "active" },
  { name: "Summit Logistics", city: "Boston", status: "active" },
  { name: "Greenleaf Pharmacy", city: "Portland", status: "overdue" },
  { name: "Bistro 21", city: "Chicago", status: "prospect" },
]

function List({ status }: { status?: string }) {
  const rows = CLIENTS.filter((c) => !status || c.status === status)
  return (
    <ul className="divide-y divide-foreground/10 px-4">
      {rows.map((c) => (
        <li key={c.name} className="flex min-h-14 flex-col justify-center">
          <span className="text-[15px] font-medium">{c.name}</span>
          <span className="text-[13px] text-muted-foreground">{c.city}</span>
        </li>
      ))}
    </ul>
  )
}

export function Clients() {
  const [tab, setTab] = useState("all")
  return (
    <div className="flex h-dvh flex-col">
      <SwipeableTabs
        value={tab}
        onValueChange={setTab}
        aria-label="Clients"
        options={[
          { value: "all", label: "All", badge: 4, content: <List /> },
          { value: "active", label: "Active", badge: 2, content: <List status="active" /> },
          { value: "overdue", label: "Overdue", badge: 1, content: <List status="overdue" /> },
          { value: "prospects", label: "Prospects", content: <List status="prospect" /> },
        ]}
      />
    </div>
  )
}

Props

  • options*
    SwipeableTabsOption[]

    The tabs and their pages: { value, label, icon?, badge?, disabled?, content }. A number badge renders a small count (hidden at 0), any other node renders as is. A disabled tab is skipped by the arrows and a swipe never settles on it.

  • value
    string

    The selected tab, controlled. A change that arrives mid-swipe is applied after the swipe settles.

  • defaultValuedefault ""
    string

    The tab selected at first, uncontrolled. Empty selects the first enabled option.

  • onValueChange
    (value: string) => void

    Fires when a swipe settles on a new page or a tab is pressed (or selected with the arrow keys).

  • variantdefault "underline"
    "underline" | "pill" | "segmented"

    The W1 tabs looks: a 2px underline under the label, a tinted capsule, or a raised thumb in a tinted track. Each one stretches between tabs while you swipe.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Trigger height 32 / 40 / 48px with matching type and icons. On touch screens md grows to 44px and sm gets a 44px hit area.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Color of the indicator, the active label and the active count badge. neutral is black in light mode and white in dark mode.

  • colordefault ""
    string

    Any CSS color for the indicator and the active label. Replaces the tone; leave empty to use it.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner shape of the pill and the segmented track and thumb. Scales with the size; full makes capsules.

  • fullWidthdefault true
    boolean

    Tabs fill the row, the phone pattern: pills and segments in equal cells, underline labels evenly spaced. Off, they size to their labels and the row scrolls sideways with edge fades when they overflow.

  • layoutdefault "fill"
    "fill" | "auto"

    fill: the tabs fill their parent's height and every page is its own vertical scroller that keeps its scroll position, like an app screen. auto: pages take their content height and the tabs are as tall as the tallest page (settings sections, short forms).

  • swipedefault true
    boolean

    Off: tabs only, the pages swap with a short fade like W1 tabs and the indicator springs between tabs.

  • swipeWithMousedefault false
    boolean

    A mouse drag swipes the pages too. Off by default so dragging over text still selects it; touch and trackpads always swipe.

  • lazydefault true
    boolean

    A page mounts the first time it is active or next to the active one, so the neighbour is ready before you swipe to it.

  • keepMounteddefault true
    boolean

    Visited pages stay mounted and keep their scroll position and what was typed. Off, pages more than one page away unmount after a swipe settles.

  • scrollToTopOnReselectdefault true
    boolean

    Tapping the active tab scrolls its page back to the top (layout="fill").

  • activationModedefault "automatic"
    "automatic" | "manual"

    automatic: the arrow keys select as they move. manual: the arrows only move focus and Enter or Space selects.

  • hapticsdefault true
    boolean

    A "selection" tick through lib/haptics when a swipe settles on a new tab. Never on taps, keys or mount.

  • aria-labeldefault "Tabs"
    string

    Names the tab list for screen readers. Pass aria-labelledby instead when a visible heading names it.

  • labels
    Partial<SwipeableTabsLabels>

    Strings read to screen readers: { swipeHint } describes the gesture on the tab list while swiping is on. The English default is "Swipe the content left or right to switch tabs."

  • classNames
    Partial<Record<"list" | "trigger" | "icon" | "label" | "badge" | "indicator" | "panels" | "panel", string>>

    Extra classes for the inner parts, e.g. { list: "px-4" } to inset pills from the screen edge.

  • className
    string

    Extra classes for the root, merged last. Override the tone vars here, e.g. [--tone:var(--info)].

Parts

  • DEFAULT_SWIPEABLE_TABS_LABELS

    constant

    The English screen reader copy: swipeHint "Swipe the content left or right to switch tabs."

  • SwipeableTabsRoot

    component

    The parts API root: takes the state (value, defaultValue, onValueChange), the look (variant, size, tone, color, radius, fullWidth), layout and behavior props, labels and classNames. Put a SwipeableTabsList and a SwipeableTabsPanels inside. Props: SwipeableTabsRootProps.

  • SwipeableTabsList

    component

    The tablist with the moving indicator. Holds the triggers; loop (default true) wraps the arrow keys. Props: ComponentProps<"div"> & { loop?: boolean }.

  • SwipeableTabsTrigger

    component

    One tab. data-state is active while the indicator is over it. Props: { value: string; icon?: ReactNode; badge?: number | ReactNode; disabled?: boolean; children }.

  • SwipeableTabsPanels

    component

    The swipe viewport: a native horizontal scroll-snap container. Its panels must be in the same order as the triggers. Takes div props.

  • SwipeableTabsPanel

    component

    One page. Mounted per lazy / keepMounted, inert while off screen (focus inside moves to the tab on screen), focusable only when it holds nothing focusable. Props: { value: string; children: ReactNode }.

  • useSwipeableTabs

    hook

    The state of the nearest tabs, for custom parts: progress is the page position (1.5 = half way between the second and third page) for a header that fades or a page counter. Signature: () => { value: string; index: number; progress: MotionValue<number>; select: (value: string) => void }.

Examples

Pill folders with unread badges

A messages screen: swipe between folders, the pill stretches from one to the next.

Segmented agenda

Day / Week / Month as a segmented control whose thumb follows the page.

Eight categories that overflow

fullWidth={false}: the tab row scrolls with edge fades and keeps the active tab in view.

Lazy panels and a mount log

Panels mount when they are active or next to it; turn keepMounted off to unmount far pages.

Swipe on and off

The same tabs with native swiping, and with swipe={false} where panels fade.

Brand underline with icons

tone="brand": an orange underline and label, with icons and a count on Inbox.

Auto height settings

layout="auto" in a card: the form keeps what you typed when you switch sections.

Dependencies

motionbadgeuse-controllable-stateuse-scroll-edgeshapticsmotiontonesui-config

Files

  • components/ui/swipeable-tabs.tsx

FAQ

Is the React Swipeable Tabs component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Swipeable Tabs?

Run npx wingo-ui@latest add swipeable-tabs. It also adds the Wingo UI items it builds on (Badge, useControllableState, useScrollEdges, Haptics, Motion presets and 2 more) and the npm package motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/swipeable-tabs.

See also

TabsSegmented ControlCarouselPull To RefreshHapticslibuseScrollEdgeshook

Used in

Mobile App HomeProfile Page

More React mobile components

Hero HeaderBottom Tab BarSticky CTA BarSlide To ConfirmAction SheetFAB
All 15 React mobile components

Articles using this component

  • Konsta UI Alternative for React: Ionic vs Wingo UI

    Comparisons and alternatives · Nov 6, 2026 · 10 min read

  • Shadcn Profile Page in React: Follow, Tabs and Edit Sheet

    Build it: screens and apps · Nov 6, 2026 · 9 min read

  • React Swipeable Tabs: Pages That Follow the Thumb

    Mobile UI in React · Oct 16, 2026 · 10 min read

  • Web App That Feels Native on a Phone: 12 React Fixes

    Mobile UI in React · Oct 13, 2026 · 11 min read

PreviousSwipe DeckNext Cart drawer

Clients

Northwind CRM · updated today at 4:40 PM

12 clientsTotal balance $195,266.45

TI
Tech Solutions Inc.
New York
$12,480.00Active
SL
Summit Logistics
Boston
$48,920.50Active
EL
Emma Carter LLC
Seattle
$0.00Prospect
GP
Greenleaf Pharmacy
Portland
$7,315.20Overdue
KB
Keystone Builders
Denver
$103,400.00Active
OB
Old Mill Bakery
Buffalo
$1,250.00Inactive
DC
Dental Art Clinic
Miami
$5,600.00Active
WB
Westside Books
Austin
$2,980.75Overdue
B
Bistro 21
Chicago
$0.00Prospect
NL
North Studio LLC
New York
$9,870.00Active
GR
Green House Realty
Austin
$0.00Negotiating
SP
Studio Pixel
Seattle
$3,450.00Active
Swipe the content left or right to switch tabs.
value: allmounted: noneactivationMode: automaticswipeWithMouselazykeepMountedscrollToTopOnReselecthapticsaria-label: Tabs