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

Haptic Button

Mobile

Haptic Button React component for Tailwind CSS v4

FABHero Header
Live props
Content
children
Appearance
variant
tone
color
size
radius
effect
rippleColor
State
loading
disabled
Layout
fullWidth
Behavior
mode
holdDuration1200ms
showSuccess
haptics
hapticKind
type
Code
import { HapticButton } from "@/components/ui/haptic-button"

<HapticButton>Send</HapticButton>
  1. Home
  2. Components
  3. Mobile
  4. Haptic Button
Pro

Haptic Button React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React haptic button that feels native: spring press, touch ripple, a haptic tick, a long-press action and a hold-to-confirm mode.

Tap it and it dips on a quick spring while a ripple spreads from under your finger; phones play a light tick (touch and pen only, a mouse click never vibrates). With onLongPress, holding a finger or pen for 450ms plays a medium tick and runs the secondary action; a right click, Shift+F10 or the ContextMenu key do the same, and the click after a long press is swallowed. In hold mode a click does nothing: hold the finger, the mouse button, Space or Enter and the button fills from the start edge; let go early and the fill springs back, a quick tap shakes the button, plays a warning tick and shows a "Hold to confirm" chip above it. Moving more than 8px, leaving the button, switching tabs or blurring the window cancels. When the fill completes the button pops, onConfirm runs (a promise shows the spinner), then a check replaces the label for 1.2s with a success tick; a rejected promise shakes with an error tick. Screen readers, voice control and switch access, which activate with a plain click and cannot hold, get a two-step path: the first activation shows and announces "Activate again to confirm", a second one within 4s confirms. Outcomes are announced to screen readers. Under reduced motion there is no dip, ripple, pop or shake; the fill still grows.

Haptic Button 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 haptic-button, together with Haptics, Motion presets, Tones, UIProvider and useLongPress and the npm packages lucide-react 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 haptic-button

Haptic Button 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 HapticButton from @/components/ui/haptic-button.

import { Trash2 } from "lucide-react"
import { HapticButton } from "@/components/ui/haptic-button"

export function DeleteClient({ onDelete }: { onDelete: () => Promise<void> }) {
  return (
    <HapticButton
      tone="danger"
      variant="soft"
      mode="hold"
      holdDuration={1200}
      leftIcon={<Trash2 />}
      fullWidth
      onConfirm={onDelete}
      labels={{
        holdHint: "Hold to delete",
        confirmed: "Deleted",
        activateAgain: "Activate again to delete",
      }}
    >
      Delete Old Mill Bakery
    </HapticButton>
  )
}

Props

  • childrendefault "Send"
    ReactNode

    The label. For icon sizes pass the icon and an aria-label. The playground swaps the sample for "Hold to send" in hold mode.

  • variantdefault "solid"
    "solid" | "soft" | "outline" | "ghost"

    The button's variants (no link: a link has no surface to ripple or fill).

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

    Semantic color from the design tokens. danger for a hold to delete, brand for a hold to pay.

  • colordefault ""
    string

    Any CSS color. Replaces the tone in every variant; text on a solid fill turns black or white by lightness.

  • sizedefault "md"
    "sm" | "md" | "lg" | "icon-sm" | "icon" | "icon-lg"

    The button's sizes: 32 / 40 / 48px, square icon sizes. On touch screens md grows to 44px and sm gets a 44px hit area.

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

    Corner shape, scaled per size like the button. The ripple and the fill follow it.

  • effectdefault "both"
    "scale" | "ripple" | "both" | "none"

    Press feedback: the 0.97 dip on a snappy spring, a ripple spreading from the touch point (keyboard presses ripple from the center), both, or none. In hold mode the fill replaces the ripple.

  • rippleColordefault ""
    string

    The ripple color. Empty uses the text color at 16%; a custom color shows at 30%.

  • loadingdefault false
    boolean

    Spinner in place of the label, width kept, aria-busy; presses and holds are ignored and focus stays. Also on while an onConfirm promise runs.

  • loadingText
    ReactNode

    Text next to the spinner while loading. The button reserves room for it.

  • disableddefault false
    boolean

    Dims the button and blocks presses, holds and long presses.

  • fullWidthdefault false
    boolean

    Stretches to the container width, the usual choice for the main action on a phone.

  • modedefault "press"
    "press" | "hold"

    press is a normal button. hold: a click does nothing; holding a finger, the mouse button, Space or Enter for holdDuration fills the button and calls onConfirm. For destructive or money actions that deserve more than a tap but less than a dialog.

  • holdDurationdefault 1200
    number

    Milliseconds of holding to confirm, clamped to 300..5000.

  • showSuccessdefault true
    boolean

    After onConfirm (and its promise) a check replaces the label for 1.2s and the button gets data-confirmed.

  • hapticsdefault true
    boolean

    Haptic ticks through lib/haptics on touch and pen presses (never for a mouse or on desktops). Turn off app-wide with configureHaptics({ enabled: false }).

  • hapticKinddefault "light"
    "selection" | "light" | "medium" | "heavy" | "success" | "warning" | "error"

    The press-mode tick. Hold mode always plays light at the start, success on confirm, warning on a too-short tap and error on a rejected promise; a long press plays medium.

  • typedefault "button"
    "button" | "submit" | "reset"

    Defaults to "button". In hold mode a submit button never submits on a click; submit from onConfirm.

  • leftIcon
    ReactNode

    An icon before the label. Unsized svgs follow the size (14 / 16 / 18px).

  • rightIcon
    ReactNode

    An icon after the label.

  • spinner
    ReactNode

    Replaces the default spinner.

  • onClick
    (event: MouseEvent<HTMLButtonElement>) => void

    Press mode only, and not while loading or disabled. Hold mode never calls it: put the action in onConfirm.

  • onConfirm
    () => (void | Promise<unknown>)

    Hold mode's action, called when the fill completes. A promise shows the loading state, then the check; a rejection shakes the button, plays an error tick and announces labels.failed.

  • onHoldStart
    () => void

    A hold began (finger, pen, mouse button, Space or Enter).

  • onHoldCancel
    () => void

    A hold ended before the fill completed: released, moved past 8px, left the button, window blurred, or disabled.

  • onLongPress
    (event: LongPressEvent<HTMLButtonElement>) => void

    Press mode's secondary action: a 450ms touch or pen hold (through useLongPress), a right click, Shift+F10 or the ContextMenu key. The click after a long press is swallowed; screen readers hear labels.longPressHint.

  • labels
    Partial<HapticButtonLabels>

    Strings: holdHint "Hold to confirm", holding "Keep holding", confirmed "Done", failed "That did not work, try again", longPressHint "Press and hold for more options", activateAgain "Activate again to confirm" (hold mode, after a click from assistive tech). DEFAULT_HAPTIC_BUTTON_LABELS holds the defaults.

  • classNames
    Partial<Record<"effects" | "ripple" | "fill" | "content" | "icon" | "spinner" | "check" | "hint", string>>

    Extra classes for the inner parts: the effects layer, each ripple, the hold fill, the label, icons, spinner, check and hint chip.

  • className
    string

    Extra classes for the button itself, merged last. Override the tone vars here, e.g. [--tone-foreground:white].

Parts

  • DEFAULT_HAPTIC_BUTTON_LABELS

    constant

    The English strings: holdHint ("Hold to confirm"), holding ("Keep holding"), confirmed ("Done"), failed, longPressHint ("Press and hold for more options") and activateAgain, which assistive tech users see in hold mode. Spread it into labels to translate a few. Type: HapticButtonLabels.

Examples

Effects

scale, ripple, both or none; ghost and soft buttons ripple over a photo too.

Hold to delete

mode="hold" with the danger tone: the row fades out only after a full hold.

Hold to pay

An async onConfirm shows the spinner, then the check. holdDuration 1500ms.

Long press for more

onLongPress opens an action sheet; right click and Shift+F10 work on desktop.

Icon buttons

Toggles with aria-pressed; the heart pops on the bouncy spring.

Sizes

sm, md and lg in solid and outline, the button's own scale.

Custom color

color="#0F766E" with a light rippleColor.

Dependencies

lucide-reactmotionhapticsmotiontonesui-configuse-long-press

Files

  • components/ui/haptic-button.tsx

FAQ

Is the React Haptic Button component free?

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

How do I install Haptic Button?

Run npx wingo-ui@latest add haptic-button. It also adds the Wingo UI items it builds on (Haptics, Motion presets, Tones, UIProvider and useLongPress) and the npm packages lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/haptic-button.

See also

ButtonSlide To ConfirmDelete ButtonHapticslibuseLongPresshookAction Sheet

More React mobile components

Install PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To Refresh
All 15 React mobile components

Articles using this component

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

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

  • Minimum Touch Target Size: WCAG, iOS, Android and Tailwind

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

  • Mobile-First React Components: 9 Rules With Code

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

  • Web Vibration API: Haptic Feedback on Android and iPhone

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

PreviousFABNext Hero Header
Invoice #2026-0412Tech Solutions Inc.
Draft
$12,480.00Due Sep 30, 2026 · tax included
To
billing@techsolutions.com
Issued
Sep 24, 2026
PDF · 2 pages

Tap for the ripple and the tick; long press or right click for onLongPress.

mode="press" · effect="both" · haptics: light
hold 1200ms · check on · ripple currentColor