@freefugga/react-smooth-swipe-button
v0.1.2
Published
Highly customizable smooth swipe button for React.
Readme
@freefugga/react-smooth-swipe-button
Highly customizable smooth swipe button for React web, created and maintained by Nishidh Jain (@freefugga).
Demo: https://smooth-swipe-demo.freefugga.workers.dev/
This package is meant for swipe-to-confirm actions in React web apps such as:
- confirm checkout
- approve destructive actions
- accept terms or continue flows
- acknowledge critical actions with intent
It keeps the drag path smooth by default with pointer events and direct DOM updates instead of depending on React re-rendering every frame.
Install
npm install @freefugga/react-smooth-swipe-buttonBasic Usage
import { SwipeButton } from "@freefugga/react-smooth-swipe-button";
export function CheckoutAction() {
return (
<SwipeButton
label="Slide to confirm payment"
trackColor="#0f766e"
onSwipeSuccess={() => {
console.log("confirmed");
}}
/>
);
}More Customization
import { SwipeButton } from "@freefugga/react-smooth-swipe-button";
export function DeleteAction() {
return (
<SwipeButton
label="Swipe to delete"
threshold={0.82}
trackColor="#991b1b"
thumbColor="#ffffff"
trailColor="#fee2e2"
labelColor="#ffffff"
height={60}
thumbSize={52}
borderRadius={18}
thumbRadius={16}
completeBehavior="stay-complete"
onSwipeSuccess={() => {
console.log("deleted");
}}
/>
);
}Threshold And Spring Control
import { SwipeButton } from "@freefugga/react-smooth-swipe-button";
export function CarefulDeleteAction() {
return (
<SwipeButton
label="Swipe to delete"
positiveOnProgress={0.9}
spring={false}
trackColor="#991b1b"
onSwipeSuccess={() => {
console.log("deleted");
}}
/>
);
}positiveOnProgress={0.9}means the user must reach 90% before success.spring={false}disables the bounce-back effect and uses a flat reset animation.
Timed Spring Example
import { SwipeButton } from "@freefugga/react-smooth-swipe-button";
export function TimedSpringAction() {
return (
<SwipeButton
label="Swipe to continue"
spring
springMs={0.5}
onSwipeSuccess={() => {
console.log("continued");
}}
/>
);
}springMsis in seconds, not milliseconds.- The bounce overshoot is capped internally to 80% of thumb width.
Live Progress Example
import { useState } from "react";
import { SwipeButton } from "@freefugga/react-smooth-swipe-button";
export function ProgressExample() {
const [progress, setProgress] = useState(0);
return (
<SwipeButton
label="Swipe to reserve seat"
positiveOnProgress={0.75}
onProgressChange={(value) => {
setProgress(value);
}}
renderThumbContent={() => (
<span>{Math.round(progress * 100)}%</span>
)}
onSwipeSuccess={() => {
setProgress(0);
}}
onSwipeCancel={() => {
setProgress(0);
}}
/>
);
}Shared Props
These props are available on both the React and React Native packages.
| Prop | Type / values | Default | What it is for |
| --- | --- | --- | --- |
| label | string | "Swipe" | Main text shown on the rail |
| disabled | boolean | false | Prevents dragging and dims the control |
| loading | boolean | false | Replaces thumb content with a loader and disables swipe |
| positiveOnProgress | number | 0.8 | Preferred success threshold; swipe progress must reach this value before success |
| threshold | number | 0.8 | Legacy alias for the success threshold; use positiveOnProgress going forward |
| spring | boolean | true | Enables the bounce-back spring reset animation |
| springMs | number | 0.5 | Total bounce-reset time in seconds, capped from 0.1 to 0.5; try 0.5, 0.4, or 0.3 |
| direction | "auto" \| "ltr" \| "rtl" | "auto" | Controls swipe direction; auto follows document direction |
| completeBehavior | "reset" \| "stay-complete" | "reset" | Whether the thumb snaps back after success or stays complete |
| disabledOpacity | number | 0.5 | Visual opacity when disabled |
| height | number | 56 | Outer track height |
| thumbSize | number | 48 | Width and height of the draggable thumb |
| thumbMargin | number | 4 | Inner gap between thumb and rail edges |
| borderRadius | number | 8 | Rail corner radius |
| thumbRadius | number | 6 | Thumb and stretched trail corner radius |
| trackColor | string | "#111827" | Rail background color |
| thumbColor | string | "#ffffff" | Thumb background color |
| trailColor | string | "#ffffff" | Color of the stretched thumb trail |
| labelColor | string | "#ffffff" | Default label text color |
| loaderColor | string | "#111827" | Default loader color |
| animation | Partial<SwipeAnimationConfig> | built-in defaults | Motion tuning for complete and reset animations |
| onSwipeStart | () => void | undefined | Called when drag starts |
| onSwipeSuccess | () => void \| Promise<void> | undefined | Called after the swipe completes successfully |
| onSwipeCancel | () => void | undefined | Called when the swipe ends before threshold |
| onSwipeEnd | (result: "success" \| "cancel") => void | undefined | Called at the end of either success or cancel |
| onProgressChange | (progress: number) => void | undefined | Reports current swipe progress from 0 to 1 |
React-only Props
| Prop | Type / values | Default | What it is for |
| --- | --- | --- | --- |
| className | string | undefined | CSS class for the outer track container |
| style | React.CSSProperties | undefined | Inline style for the outer component |
| trackStyle | React.CSSProperties | undefined | Inline style specifically for the track |
| labelStyle | React.CSSProperties | undefined | Inline style for the label area |
| thumbStyle | React.CSSProperties | undefined | Inline style for the draggable thumb |
| trailStyle | React.CSSProperties | undefined | Inline style for the stretched trail |
| labelClassName | string | undefined | CSS class for the label wrapper |
| thumbClassName | string | undefined | CSS class for the thumb |
| trailClassName | string | undefined | CSS class for the trail |
| thumbContent | React.ReactNode | chevron | Replaces the default thumb icon |
| loadingIndicator | React.ReactNode | spinner | Replaces the default loader UI |
| successContent | React.ReactNode | undefined | Optional content shown after success |
| renderLabel | (state) => React.ReactNode | undefined | Custom label renderer using live swipe state |
| renderThumbContent | (state) => React.ReactNode | undefined | Custom thumb renderer using live swipe state |
| ariaLabel | string | label | Accessibility label for the control |
Animation Config
animation accepts these optional keys:
| Key | Type | Default | What it controls |
| --- | --- | --- | --- |
| completeDuration | number | 100 | Duration of the final snap to success |
| resetDelay | number | 0 | Delay before resetting after success |
| resetDuration | number | 220 | Duration of the reset animation |
| resetEasing | string | "cubic-bezier(0.22, 1, 0.36, 1)" | CSS easing used for reset |
| springDamping | number | 15 | Legacy shared config value kept for API compatibility |
| springStiffness | number | 120 | Legacy shared config value kept for API compatibility |
Render Callback State
renderLabel and renderThumbContent receive:
type SwipeButtonRenderState = {
progress: number;
thumbX: number;
maxX: number;
isDragging: boolean;
isComplete: boolean;
isLoading: boolean;
isDisabled: boolean;
isRTL: boolean;
};Imperative Ref API
import { useRef } from "react";
import {
SwipeButton,
type SwipeButtonRef,
} from "@freefugga/react-smooth-swipe-button";
export function Example() {
const ref = useRef<SwipeButtonRef>(null);
return (
<SwipeButton
ref={ref}
label="Swipe to continue"
onSwipeSuccess={() => {
console.log(ref.current?.getProgress());
}}
/>
);
}Available methods:
reset()complete()getProgress()
Notes
- Use
@freefugga/react-native-smooth-swipe-buttonfor React Native or Expo apps. - Use
@freefugga/smooth-swipe-coreonly if you need the shared logic layer directly. - Live render callbacks are powerful, but they can add more JS work during drag than the default path.
positiveOnProgressis the clearer threshold prop for new integrations.thresholdstill works as a compatibility alias.spring={true}is the default.springMs={0.5}is the default bounce duration, and values are capped at0.5.
License
MIT, copyright Nishidh Jain.
