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

Navigation

Tabs

Navigation

Tabs React component for Tailwind CSS v4

Table Of ContentsTopbar
Live props
State
defaultValue
Content
aria-label
Appearance
variant
size
tone
color
radius
Layout
orientation
responsive
fullWidth
scrollable
Behavior
animateContent
activationMode
keepMounted
Code
import { Tabs } from "@/components/ui/tabs"

<Tabs />
  1. Home
  2. Components
  3. Navigation
  4. Tabs
Free

Tabs React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

React tabs with a sliding indicator that switch views of one page: underline for sections, pill for filters, segmented for a compact switch.

Click or tap a tab and the indicator glides over to it on a smooth spring while the new panel slides in from the side you moved toward and the old one fades away. Tabs dip slightly under the finger. With the keyboard, Tab lands on the active tab, Left and Right (Up and Down when vertical) move between tabs, Home and End jump to the ends, and Tab again moves into the panel; in manual mode the arrows only move focus and Enter or Space opens the tab. When the tabs do not fit, the row scrolls sideways with soft fades at the edges and keeps the active tab in view. On a phone, triggers are 44px tall, and vertical tabs that get narrower than 32rem (a phone, a side panel) turn into a scrolling row above the content. Count badges roll when their number changes. With dir set to rtl the arrow keys, the edge fades and the panel slide mirror.

Tabs is a free React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add tabs, together with UIProvider, Badge, Motion presets, Tones, useControllableState and 1 more and the npm packages @radix-ui/react-tabs and 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 tabs

Usage

Import Tabs from @/components/ui/tabs.

import { Tabs, type TabsOption } from "@/components/ui/tabs"

const options: TabsOption[] = [
  { value: "active", label: "Active", badge: 12, content: <p>12 listings live in New York.</p> },
  { value: "pending", label: "Pending", badge: 3, content: <p>3 listings waiting for review.</p> },
  { value: "sold", label: "Sold", content: <p>Last sale: Sep 2, 2026.</p> },
  { value: "expired", label: "Expired", content: <p>No expired listings.</p> },
]

export function MyListings() {
  return <Tabs options={options} aria-label="My listings" />
}

Props

  • options
    TabsOption[]

    The tabs: { value, label, icon?, badge?, disabled?, content? }. A number badge renders a small count that hides at 0; content is the panel. Leave content out to use the tabs as a filter or a switch.

  • value
    string

    The active tab, when you control it. A value that is not in options leaves no tab active.

  • defaultValuedefault ""
    string

    The tab active on the first render when uncontrolled. Empty, or a value that is not in options (a stale link, options that load later), starts on the first enabled option.

  • onValueChange
    (value: string) => void

    Called with the new value. Also called with the first enabled tab when the active one is removed from options.

  • aria-labeldefault ""
    string

    Names the tab list for screen readers when no visible heading does, e.g. "My listings". To name it after a heading on the page, pass aria-labelledby with the heading's id instead; both land on the list.

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

    underline: a 2px bar under the active label over a hairline baseline, for page sections. pill: the active tab gets a tinted pill, for filters. segmented: a raised thumb in a tinted track, for a compact two to five way switch.

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

    Trigger height 32 / 40 / 48px with matching padding, type and icon size. On touch screens md grows to 44px and sm keeps its look with a 44px hit area.

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

    The indicator color: the underline bar, the pill tint, the segmented thumb, and the active badge. neutral is black in light mode and white in dark mode.

  • colordefault ""
    string

    Any CSS color for the indicator. Replaces the tone; text on a segmented thumb turns black or white by lightness. Leave empty to use the tone.

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

    Corner shape of pills, the segmented track and the focus ring. Scales with the size (md is 8 / 10 / 12px); the segmented thumb is 2px tighter than its track. full makes capsules.

  • orientationdefault "horizontal"
    "horizontal" | "vertical"

    vertical puts the tabs in a column beside the content (at least 176px wide, labels start-aligned) for settings pages; the underline becomes a bar on the start edge and arrow keys go up and down.

  • responsivedefault true
    boolean

    Vertical tabs only: when the tabs are narrower than 32rem (a CSS container query on their own width, so a phone, a side panel or a narrow card alike) the column turns into a scrolling row above the content and the arrow keys follow. Turn it off to keep the column everywhere.

  • fullWidthdefault false
    boolean

    Triggers share the width equally. Segmented plus fullWidth is the usual header switch on a phone.

  • scrollabledefault true
    boolean

    A row that does not fit scrolls sideways with edge fades and keeps the active tab in view (the list scrolls, never the page). Off, the triggers wrap onto a new line instead.

  • animateContentdefault true
    boolean

    Panels slide 16px in the direction you moved and fade; the leaving panel steps out of the flow so nothing jumps. Off, panels swap instantly. Under reduced motion they only fade.

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

    automatic opens a tab as soon as the arrow keys focus it. manual only moves focus and opens on Enter or Space, for panels that are slow to render.

  • keepMounteddefault false
    boolean

    Keeps inactive panels in the DOM (hidden), so a half-filled form keeps what was typed when you switch away and back.

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

    Extra classes for the inner parts: the scrolling list, every trigger, the indicator, icons, labels, badges and panels.

  • className
    string

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

Parts

  • TabsRoot

    component

    The compound root: value, defaultValue, onValueChange, variant, size, tone, color, radius, orientation, responsive, fullWidth, scrollable, animateContent, activationMode, keepMounted, classNames. Wrap a TabsList and your TabsContent panels in it, and pass defaultValue (or value): unlike Tabs, the root does not pick the first tab for you. Props: TabsRootProps.

  • TabsList

    component

    The scrolling tab list (role tablist). Renders the sliding indicator for you; loop (default true) lets the arrow keys wrap. Give it an aria-label when no heading names it. Props: { loop?: boolean } & ComponentProps<"div">.

  • TabsTrigger

    component

    One tab. A number badge renders a count badge in the active tone; any other node renders as is. Props: { value: string; icon?: ReactNode; badge?: ReactNode | number; disabled?: boolean; children: ReactNode }.

  • TabsContent

    component

    The panel for one value, focusable with a visible ring. Leaving panels are positioned against the nearest positioned parent (TabsRoot is one). Props: { value: string; children: ReactNode }.

Examples

Variants

underline for page sections, pill for filters, segmented for a compact switch.

Scrolling filters on a phone

Pills with icons in a 360px card: the row scrolls with edge fades and keeps the chosen category in view.

Phone header switch

segmented plus fullWidth shares the width equally; the panel slides the way you moved.

Vertical settings

A column beside the forms when there is room, a scrolling row when the tabs are narrower than 32rem (like in this panel). keepMounted keeps what you typed.

Live badges

A number badge rolls to the new count and hides at 0.

Manual activation, from the parts

TabsRoot, TabsList, TabsTrigger and TabsContent. The arrow keys only move focus; Enter or Space opens the tab, for panels that are slow to render.

Custom color

Any CSS color or a tone paints the indicator: a violet underline, a brand thumb, a teal pill.

Sizes and radius

32, 40 and 48px; the segmented thumb stays 2px inside its track at every radius.

Dependencies

@radix-ui/react-tabsmotionui-configbadgemotiontonesuse-controllable-stateuse-scroll-edges

Files

  • components/ui/tabs.tsx

FAQ

Is the React Tabs component free?

Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.

How do I install Tabs?

Run npx wingo-ui@latest add tabs. It also adds the Wingo UI items it builds on (UIProvider, Badge, Motion presets, Tones, useControllableState and 1 more) and the npm packages @radix-ui/react-tabs and motion. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/tabs.json.

How does Tabs behave on phones?

Vertical tabs only: when the tabs are narrower than 32rem (a CSS container query on their own width, so a phone, a side panel or a narrow card alike) the column turns into a scrolling row above the content and the arrow keys follow. Turn it off to keep the column everywhere. The responsive prop controls this and is on by default.

See also

Segmented ControlToggle GroupBadgeToggleuseScrollEdgeshookMotion presetslib

Used in

Schema BuilderNotification CenterMembers tableSettings LayoutAutomation Builder PageCRM Contact PageInbox PageUsers Admin PageBug Bounty PageCommunity PageDevelopers PageDocs Home Page

and 2 more.

More React navigation components

TopbarWorkspace SwitcherBreadcrumbScroll ProgressProximity SidebarBounce Sidebar
All 19 React navigation components

Articles using this component

  • React Controllable State Hook: Code and 3 Pitfalls

    Next.js and Tailwind CSS · Oct 16, 2026 · 10 min read

  • React Swipeable Tabs: Pages That Follow the Thumb

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

  • React Segmented Control: iOS Thumb, Drag and When to Use It

    Mobile UI in React · Oct 9, 2026 · 8 min read

PreviousTable Of ContentsNext Topbar

My listings

Emma Carter · 17 listings

  • 2-bedroom apartment, Harlem

    New York·Sep 12, 2026

    $612,000248
  • City bike, 54 frame

    Seattle·Sep 18, 2026

    $32096
  • Gray sleeper sofa

    New York·Sep 20, 2026

    $45057

Automatic: the arrow keys open each tab as focus lands on it