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

Onboarding Carousel

Mobile

Onboarding Carousel React component for Tailwind CSS v4

Notification BannerPicker Wheel
Live props
Layout
layout
modal
State
defaultOpen
defaultIndex0
Appearance
indicator
indicatorPosition
nextVariant
size
align
tone
color
radius
Content
showSkip
showBack
Behavior
skipTo
parallax
haptics
persistKey
Code
import { OnboardingCarousel } from "@/components/ui/onboarding-carousel"

<OnboardingCarousel />
  1. Home
  2. Components
  3. Mobile
  4. Onboarding Carousel
Pro

Onboarding Carousel React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React onboarding carousel of first-run slides for an app or PWA: swipe or tap Next, follow the dots, Skip any time, then Get started.

Swipe the slides with a finger, or drag them with a mouse or trackpad (embla's physics, vertical page scrolling is never blocked); the illustration trails the swipe at 0.6x and the text at 0.9x, and each slide's background color cross-fades into the next as you go; a finger swipe that lands on a new slide ticks once (haptics). Tap Next to advance; on the last slide it becomes Get started with a quick fade (the circle variant grows from a round arrow, ringed by the progress, into a pill). Skip in the top corner jumps to the last slide (or finishes right away with skipTo="complete") and fades out on the last slide; with showBack a back chevron slides in before Next from the second slide. Tap a dot or a story bar to jump there. With focus anywhere inside, Left and Right move one slide and Home / End jump to the ends; Enter or Space on Next advances, and focus is handed to Next when Skip or Back disappear. Each settled slide is announced ("Slide 2 of 4: ..."), off-screen slides are inert. Finishing fades the whole thing out before onComplete runs; with persistKey the finish (or a skip that ends it: skipTo="complete", closing the modal) is remembered and it never shows again. As a modal it opens from its trigger as a centered card on desktop and slides up as a full screen on a phone, focus lands on Next, and Escape or a click outside counts as Skip. Under reduced motion slides jump, there is no parallax, and everything else fades.

Onboarding Carousel 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 onboarding-carousel, together with Button, Carousel, Haptics, useControllableState, useMediaQuery and 4 more and the npm packages @radix-ui/react-dialog, @radix-ui/react-visually-hidden, motion and lucide-react.

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 onboarding-carousel

Onboarding Carousel 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 OnboardingCarousel from @/components/ui/onboarding-carousel.

"use client"

import { useRouter } from "next/navigation"
import { OnboardingCarousel, type OnboardingSlide } from "@/components/ui/onboarding-carousel"

const slides: OnboardingSlide[] = [
  {
    id: "clients",
    title: "Every client in one place",
    description: "Companies, contacts and balances, the same on your phone and at the office.",
    image: "/onboarding/clients.svg",
    background: "var(--info-soft)",
  },
  {
    id: "invoices",
    title: "Invoices in 30 seconds",
    description: "Issue the invoice from your phone and we email it to your client.",
    image: "/onboarding/invoices.svg",
    background: "var(--success-soft)",
  },
  {
    id: "offline",
    title: "Works offline too",
    description: "Everything syncs when you are back online.",
    image: "/onboarding/offline.svg",
  },
]

export default function Welcome() {
  const router = useRouter()
  return (
    <OnboardingCarousel
      layout="screen"
      slides={slides}
      persistKey="onboarding-v1"
      showBack
      labels={{ skip: "Skip", next: "Continue", back: "Back", done: "Get started", region: "Welcome" }}
      onComplete={() => router.replace("/home")}
    />
  )
}

Props

  • slides*
    OnboardingSlide[]

    { id, title, description?, media?, image?, imageAlt?, background?, foreground? }. media is any node (a composed ui vignette, an svg); image is contained in the media area, or full-bleed behind the text with a bottom scrim when foreground is "light". background is any CSS color or token (var(--info-soft)) and cross-fades while swiping; without one the root's surface shows.

  • layoutdefault "fill"
    "fill" | "screen" | "card"

    fill takes the parent's height (a route of its own inside an app shell). screen is min-h-dvh with safe-area padding at the top and bottom. card is a 420 x 640px rounded surface, capped at 85dvh.

  • modaldefault false
    boolean

    Renders it in a Radix dialog with a focus trap and a hidden title: a full screen below 768px that slides up, a centered card from 768px. Closing (Escape, the overlay) counts as Skip. layout is ignored.

  • defaultOpendefault false
    boolean

    Modal mode: open on mount. The playground turns modal on with it.

  • open
    boolean

    Modal mode, controlled. Pair with onOpenChange.

  • onOpenChange
    (open: boolean) => void

    Modal mode: the trigger opened it, or Skip, Escape, the overlay or the last button closed it.

  • trigger
    ReactNode

    Modal mode: the element that opens it, rendered asChild (a Button).

  • indicatordefault "dots"
    "dots" | "bars" | "progress" | "counter"

    The carousel's indicators: dots whose active one stretches, story bars, one progress bar, or a 2 / 4 counter. Hidden with a single slide.

  • indicatorPositiondefault "bottom"
    "top" | "bottom"

    bottom sits above Next (beside the circle). top puts it in the header next to Skip, the stories look.

  • nextVariantdefault "button"
    "button" | "circle"

    button: a full-width Next that becomes Get started. circle: a round arrow ringed by the progress that grows into the Get started pill on the last slide.

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

    Title 24 / 28 / 32px, description 15 / 16 / 17px, indicators, and the Next button (40 / 48 / 48px, 44px on touch at sm; the circle 48 / 56 / 64px).

  • aligndefault "center"
    "center" | "start"

    Text alignment of the title and description.

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

    The Next / Get started button, the circle's ring and the active dot or bar.

  • colordefault ""
    string

    Any CSS color. Replaces the tone; text on the button turns black or white by lightness.

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

    Corner shape of the Next and Back buttons in the button variant (the circle is always round).

  • showSkipdefault true
    boolean

    The Skip button in the header. It fades out on the last slide.

  • showBackdefault false
    boolean

    A back chevron before Next from the second slide.

  • skipTodefault "last"
    "last" | "complete"

    Skip jumps to the last slide, or finishes right away (fade out, then onSkip and onComplete).

  • parallaxdefault true
    boolean

    While swiping, the media moves at 0.6x the slide and the text at 0.9x. Off under reduced motion.

  • hapticsdefault true
    boolean

    A "selection" tick through lib/haptics when a finger or pen swipe lands on a new slide. Never on a mouse drag, taps, keys or mount; silent on desktops.

  • persistKeydefault ""
    string

    When set, a finish or a skip that ends the tour (skipTo="complete", closing the modal) is remembered in localStorage (use-persisted-state) and nothing renders afterwards; a modal stays closed unless its trigger opens it. The server renders the slides; a remembered finish fades them out after hydration (a modal never flashes). Clear it with writePersisted(key, null).

  • index
    number

    The current slide, controlled. A jump scrolls there (instantly under reduced motion); values past the end clamp.

  • defaultIndexdefault 0
    number

    The first slide shown, uncontrolled. The tour also returns here after it finishes.

  • onIndexChange
    (index: number) => void

    Every settled change: a swipe, Next, Back, Skip, a dot or a key.

  • onComplete
    () => void

    The last slide's button (and Skip with skipTo="complete"), after the fade out.

  • onSkip
    (index: number) => void

    Skip was pressed (or the modal was closed) on this slide.

  • labels
    Partial<OnboardingCarouselLabels>

    skip "Skip", next "Next", back "Back", done "Get started", region "Welcome" (the region label and the modal title fallback), slide(i, n) "Slide 2 of 4", goTo(i) "Go to slide 3". Pass only the keys you translate.

  • setApi
    (api: CarouselApi) => void

    The embla api, for custom controls.

  • classNames
    Partial<Record<"header" | "skip" | "slide" | "media" | "title" | "description" | "footer" | "indicator" | "back" | "next", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root, merged last. Override the tone vars here, e.g. [--tone:#0f766e].

Parts

  • OnboardingSlide

    type

    One slide: id, title, description, media (any node) or image with imageAlt, background (any CSS color, cross-fades while swiping) and foreground ("light" for photos and dark backgrounds).

  • DEFAULT_ONBOARDING_CAROUSEL_LABELS

    constant

    The English copy: skip "Skip", next "Next", back "Back", done "Get started", region "Welcome" and the slide announcements.

Examples

Circle next, brand tone

A round arrow ringed by the progress that grows into the Get started pill on the last slide.

As a modal

A trigger opens it as a centered card on desktop and a full screen on a phone.

Photo slides

Full-bleed photos with foreground="light": white text and controls over a scrim.

Stories bars on top

indicatorPosition="top" puts the bars in the header next to Skip.

Remembered once seen

persistKey stores a finish or a skip; Reset clears it and the tour comes back.

Start aligned, large, default labels

align="start", size="lg", the progress bar and the built-in default labels.

Dependencies

@radix-ui/react-dialog@radix-ui/react-visually-hiddenmotionlucide-reactbuttoncarouselhapticsuse-controllable-stateuse-media-queryuse-persisted-statemotiontonesui-config

Files

  • components/ui/onboarding-carousel.tsx

FAQ

Is the React Onboarding Carousel component free?

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

How do I install Onboarding Carousel?

Run npx wingo-ui@latest add onboarding-carousel. It also adds the Wingo UI items it builds on (Button, Carousel, Haptics, useControllableState, useMediaQuery and 4 more) and the npm packages @radix-ui/react-dialog, @radix-ui/react-visually-hidden, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/onboarding-carousel.

How does Onboarding Carousel behave on phones?

As a modal it opens from its trigger as a centered card on desktop and slides up as a full screen on a phone, focus lands on Next, and Escape or a click outside counts as Skip.

See also

CarouselTourInstall PromptStepsusePersistedStatehook

Used in

Onboarding Flow

More React mobile components

Picker WheelPull To RefreshSwipe DeckSwipeable TabsHero HeaderBottom Tab Bar
All 15 React mobile components

Articles using this component

  • Embla Carousel React: Accessible Slides, No Layout Shift

    Component guides · Oct 19, 2026 · 9 min read

  • React Onboarding Flow: Slides, Permissions, Install

    Build it: screens and apps · Oct 16, 2026 · 9 min read

PreviousNotification BannerNext Picker Wheel
Acme CRM

Hi, Emma

Thursday, Sep 24, 2026 · 3 invoices to send today

Active clients128
Overdue invoices7
Collected today$4,380
TTech Solutions LLC$12,480.00Active
SSummit Logistics Inc.$48,920.50Active
WWellness Pharmacy$7,315.20Overdue

Every client in one place

Companies, contacts and balances, the same on your phone and at the office.

Invoice #2026-0412Sep 24, 2026
Sent to client
ClientTech Solutions LLCTotal$12,480.00

Invoices in 30 seconds

Issue the invoice from your phone and we email it to your client.

Collected in September$86,420+12.4%
MayJunJulAugSep

Payments, up to date

See who paid and who is late without opening a spreadsheet.

Synced at 4:40 PM

Works offline too

Add clients and invoices with no signal; everything syncs when you are back online.

slide 1/4skipTo: lastparallax: onhaptics: onpersistKey: noneno event yet