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

Picker Wheel

Mobile

Picker Wheel React component for Tailwind CSS v4

Onboarding CarouselPull To Refresh
Live props
Appearance
size
visibleRows5
perspective
band
fade
showColumnLabels
tone
color
State
disabled
Behavior
loop
haptics
Content
aria-label
Code
import { PickerWheel } from "@/components/ui/picker-wheel"

<PickerWheel />
  1. Home
  2. Components
  3. Mobile
  4. Picker Wheel
Pro

Picker Wheel React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React iOS-style picker wheel whose columns spin with momentum and snap on a 3D cylinder.

Flick a column with a finger and it spins with the phone's own momentum, rows tilting away on a cylinder and fading toward the edges, then clicks into place on a row; each row that reaches the center ticks under the thumb where the phone can vibrate (Android, iOS 17.4+). The value changes once, when the wheel comes to rest, never per row passed; a wheel that lands on a disabled (sold out) row glides on to the nearest one that is not. With a mouse, scroll the wheel, click-drag it up or down and let go to keep it spinning with inertia, or click any visible row to bring it to the center. Each column is one Tab stop: ArrowUp / ArrowDown move a row, PageUp / PageDown five, Home / End jump to the ends, typing jumps to the next option that starts with the text ("19" then "199" on a year wheel), ArrowLeft / ArrowRight move to the neighbor column. Looping columns wrap around from the last row to the first. PickerWheelField shows the value in a form field: a tap opens a bottom sheet on phones with the wheels, Done on top and Cancel under it (Enter on a wheel works like Done; dragging the sheet away keeps the old value), a click or Enter / Space / Alt+ArrowDown opens a popover on desktop where the wheel applies live and Enter or Done closes it, Escape closes and returns focus to the field; the field is announced with its label and its current value. With reduced motion the rows stay flat and programmatic moves jump instead of gliding.

Picker Wheel 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 picker-wheel, together with Button, Drawer, Field, useControllableState, useLocale and 6 more and the npm packages @radix-ui/react-popover, 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 picker-wheel

Picker Wheel 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 PickerWheel from @/components/ui/picker-wheel.

import { useState } from "react"
import { PickerWheel, PickerWheelField, pickerRange, useDateWheel } from "@/components/ui/picker-wheel"

const hours = pickerRange(8, 19, { pad: 2 })
const minutes = pickerRange(0, 55, { step: 5, pad: 2 })

export function Appointment() {
  const [time, setTime] = useState(["10", "30"])
  const birthday = useDateWheel({ defaultValue: "1990-05-17", locale: "en-US" })

  return (
    <div className="flex flex-col gap-6">
      <PickerWheel aria-label="Date of birth" {...birthday} />
      <PickerWheel
        aria-label="Appointment time"
        loop
        value={time}
        onValueChange={(value) => setTime(value)}
        columns={[
          { id: "hour", label: "h", options: hours },
          { id: "minute", label: "min", options: minutes },
        ]}
      />
      <PickerWheelField
        label="Model year"
        name="year"
        placeholder="Choose the year"
        options={pickerRange(2026, 1990)}
      />
    </div>
  )
}

Props

  • columns
    PickerWheelColumnDef[]

    The wheels, left to right: { id, options, label?, width?: "auto" | number, align?: "start" | "center" | "end", loop?, showLabel? }. label names the listbox and shows beside the center row ("h", "min"). Required unless options is set.

  • options
    PickerWheelOption[]

    Shortcut for a single column. Each option is { value, label, textValue?, disabled? }; textValue feeds type-to-jump and the field text when the label is not plain text.

  • value
    string[]

    One value per column, in column order (controlled). A value that is not an option shows the first enabled option and fires nothing until the user moves.

  • defaultValue
    string[]

    The starting values while uncontrolled; defaults to the first enabled option of each column.

  • onValueChange
    (value: string[], column: string) => void

    Fires once per settle with every column's value and the id of the column that moved, never per row passed.

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

    Row height 36 / 44 / 52px with 15 / 17 / 20px text. md matches the wheels of the time picker's sheet.

  • visibleRowsdefault 5
    number

    How many rows show (odd, 3 to 9). The wheel is rowHeight x visibleRows tall: 220px at md with 5 rows.

  • perspectivedefault "ios"
    "ios" | "subtle" | "flat"

    How far rows tilt away from the center: 20 / 10 / 0 degrees per row, capped at 55. Opacity falls to 0.35 either way.

  • banddefault true
    boolean

    The rounded selection band behind the center row, shared by all columns.

  • fadedefault true
    boolean

    Fades the top and bottom rows out with a mask, like the iOS picker.

  • showColumnLabelsdefault true
    boolean

    Shows each column's label right after its widest option, beside the center row ("2 h 30 min"). A column's own showLabel wins; date columns from useDateWheel turn it off.

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

    neutral: a light gray band and foreground text. Other tones tint the band and color the centered row.

  • colordefault ""
    string

    Any CSS color; replaces the tone for the band and the centered row.

  • disableddefault false
    boolean

    Dims the wheel, stops scrolling and removes the columns from the Tab order.

  • loopdefault false
    boolean

    Every column wraps around, so 59 is followed by 00 again. A column's own loop wins; lists shorter than half the window never loop.

  • hapticsdefault true
    boolean

    A selection tick each time a new row reaches the center while a finger, a mouse drag or a wheel drives it; never for keys, clicks or outside value changes.

  • aria-labeldefault ""
    string

    The name of the group of wheels ("Date of birth"); each column is named by its own label.

  • labels
    Partial<PickerWheelLabels>

    Strings: empty "No options", and for the field done "Done", cancel "Cancel", placeholder "Select". Defaults in DEFAULT_PICKER_WHEEL_LABELS.

  • classNames
    Partial<Record<"band" | "column" | "option" | "columnLabel", string>>

    Extra classes for the band, each column listbox, each option row and the column labels.

  • className
    string

    Extra classes for the root, merged last (width, margins, tone var overrides).

Parts

  • PickerWheelField

    component

    The wheel as a form field: everything PickerWheel takes plus label, description, error, required, invalid, name (a hidden input with the values joined by formatValue or ","), placeholder, variant ("soft" | "outline" | "ghost"), radius, responsive (a bottom sheet below 768px, default true), open / defaultOpen / onOpenChange, sheetTitle, container and formatValue(value) for the text in the box. The sheet commits on Done (or Enter on a wheel); the desktop popover applies live and closes on Enter or Done. Either way the wheel gets focus on open and the field gets it back on close. Props: PickerWheelFieldProps.

  • PickerWheelColumn

    component

    One wheel on its own, with the time picker wheel's shape: options, value / defaultValue / onValueChange(value: string), label, showLabel, align, loop and the appearance props (size, visibleRows, perspective, band, fade, tone, color, haptics, disabled). Props: PickerWheelColumnProps.

  • useDateWheel

    hook

    Day, month and year columns in the locale's order (ro-RO day / month / year, en-US month / day / year), month names in the locale, days that follow the month (29.02 only in leap years; a day that no longer exists becomes the last one) and years between min and max (ISO dates, default 1900-01-01 to 2100-12-31). Values are local ISO dates ("2026-09-24"). Spread it: <PickerWheel {...useDateWheel({ defaultValue: "1990-05-17", locale: "ro-RO" })} />; date is the current ISO date (not spread). Signature: (options: { value?, defaultValue?, onValueChange?(date: string), locale?, min?, max?, order?, labels? }) => { columns, value, onValueChange, date }.

  • pickerRange

    function

    Numbered options either way: pickerRange(0, 23, { pad: 2 }) gives "00" to "23"; pickerRange(0, 55, { step: 5, pad: 2 }) gives minutes every 5; format(n) makes the label, disabled(n) blocks a number. Signature: (start: number, end: number, options?: { step?, pad?, format?, disabled? }) => PickerWheelOption[].

  • DEFAULT_PICKER_WHEEL_LABELS

    constant

    The English defaults of labels, for translating once. Type: PickerWheelLabels.

Examples

Time, 24h with loop

Hours 08 to 19 and minutes every 5 that wrap around, labeled h / min beside the center row.

Quantity

One large column from 1 to 99 with a running total that updates when the wheel settles.

As a form field

PickerWheelField: a bottom sheet with Done / Cancel on phones, a popover that applies live on desktop.

Sizes and sold-out options

sm / md / lg rows (36 / 44 / 52px). Disabled options are struck through and the wheel glides past them.

Perspectives

ios, subtle and flat tilt, seven visible rows. Reduced motion always renders flat.

Duration in a tone

tone="brand" tints the band and the centered rows; the labels sit right after the widest number.

Locale order

useDateWheel follows the locale: day, month, year in ro-RO and month, day, year in en-US, with the month names of each.

Dependencies

@radix-ui/react-popovermotionlucide-reactbuttondrawerfielduse-controllable-stateuse-localeuse-media-querydate-utilshapticsmotiontonesui-config

Files

  • components/ui/picker-wheel.tsx

FAQ

Is the React Picker Wheel component free?

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

How do I install Picker Wheel?

Run npx wingo-ui@latest add picker-wheel. It also adds the Wingo UI items it builds on (Button, Drawer, Field, useControllableState, useLocale and 6 more) and the npm packages @radix-ui/react-popover, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/picker-wheel.

How does Picker Wheel behave on phones?

PickerWheelField shows the value in a form field: a tap opens a bottom sheet on phones with the wheels, Done on top and Cancel under it (Enter on a wheel works like Done; dragging the sheet away keeps the old value), a click or Enter / Space / Alt+ArrowDown opens a popover on desktop where the wheel applies live and Enter or Done closes it.

See also

Time PickerDate PickerSlide To ConfirmDrawerHapticslib

More React mobile components

Pull To RefreshSwipe DeckSwipeable TabsHero HeaderBottom Tab BarSticky CTA Bar
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

  • React Time Picker: Typed Input, 12 or 24 Hours by Locale

    Component guides · Oct 19, 2026 · 8 min read

  • Best React Component Library for Mobile in 2026: 9 Compared

    Comparisons and alternatives · Oct 9, 2026 · 12 min read

  • React Wheel Picker: iOS Wheels for Times and Dates

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

PreviousOnboarding CarouselNext Pull To Refresh

Date of birth

Profile of Emma Carter, Acme CRM

January
February
March
April
May
June
July
August
September
October
November
December
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
May 17, 199036 years
haptics onloop offcolumn labels on

Flick, drag or scroll a column; arrows and typing work too