@splitin/react-mobile-interactions
v0.1.0
Published
Reusable React primitives for mobile gestures, native motion, route transitions, and overlay back handling.
Maintainers
Readme
Mobile interactions that feel native — package first, product later
Reusable React primitives for swipe gestures, edge-back navigation, overlay back layers, route transitions, and motion presets. V1 is package-only: install it, ship it, keep versioning it.
Built internally, open sourced so others can use the same gestures SplitIn uses. A tool specialist owns this folder end to end. It can stay MIT and later also power a hosted SplitIn experience.
- Swipe tabs: left forward, right back; inputs and maps ignored by default.
- Overlay back stack: highest priority layer wins.
- Edge-swipe back: router-agnostic, haptic when available.
- Out of the box: Framer Motion presets that respect
prefers-reduced-motion.
Table of contents
- Getting started
- Use cases
- Swipe tabs
- Overlay back layers
- Edge-swipe back
- Route transition
- Motion presets
- Careers
Getting started
npm install @splitin/react-mobile-interactions framer-motionPeer dependencies: react, react-dom, framer-motion.
From this hub (source only; dist/ is gitignored):
git clone https://github.com/splitintech/open-internal-tools.git
cd open-internal-tools/react-mobile-interactions
npm install
npm run build
npm testWork in sync with other contributors and agents. PRs stay in react-mobile-interactions/.
Use cases
Ten ways developers integrate @splitin/react-mobile-interactions into a React or PWA app:
- Listing detail tabs — swipe between photos, details, and activity with
useSwipeableTabswithout stealing taps from buttons. - React Router edge-back —
MobileEdgeBackHandler+navigate(-1)for an iOS-style left-edge pop. - Filter / sort bottom sheet — register the sheet on the overlay back stack so Android/iOS back closes it first.
- Modal above sheet — two
useMobileBackLayerpriorities; dialog wins, then the sheet. - Onboarding carousel — swipe forward/back across steps; ignore inputs so form fields still type.
- Inbox vs thread — swipe between list and conversation panes in a messaging PWA.
- Map + list dual pane — bind swipe on the list; maps, canvases, and iframes stay ignored by default.
- Checkout stepper — swipe between cart, address, and pay;
canStartblocks back when the flow is complete. - Settings / profile routes — wrap pages in
MobileRouteTransitionfor sheet-up motion withnativeSprings.smooth. - Accessible dialogs — use
nativeDialogVariants/shouldAnimate()so reduced-motion users skip transforms.
Swipe tabs
import { useSwipeableTabs } from "@splitin/react-mobile-interactions";
function MobileTabs({ tab, setTab }: { tab: "details" | "activity"; setTab: (tab: "details" | "activity") => void }) {
const swipe = useSwipeableTabs({
values: ["details", "activity"],
activeValue: tab,
onValueChange: setTab,
});
return <section {...swipe.bind}>...</section>;
}Left swipes move forward, right swipes move backward, and interactive controls such as inputs, buttons, media, tablists, maps, iframes, and canvases are ignored by default.
Overlay back layers
import {
MobileBackProvider,
useMobileBackController,
useMobileBackLayer,
} from "@splitin/react-mobile-interactions";
function Sheet({ open, close }: { open: boolean; close: () => void }) {
useMobileBackLayer({
id: "sheet",
priority: 20,
enabled: open,
onBack: () => {
close();
return true;
},
});
return open ? <div role="dialog">...</div> : null;
}
function BackButton() {
const controller = useMobileBackController();
return <button onClick={() => controller.triggerBack()}>Back</button>;
}
export function App() {
return (
<MobileBackProvider>
<BackButton />
<Sheet open close={() => undefined} />
</MobileBackProvider>
);
}Layers register by id; the highest enabled priority wins. If a layer returns false from onBack, the controller continues to the next enabled layer.
Edge-swipe back
MobileEdgeBackHandler is router-agnostic. Wire the back behavior in user-land:
import { MobileEdgeBackHandler } from "@splitin/react-mobile-interactions";
import { useNavigate } from "react-router-dom";
function RouterBackGesture() {
const navigate = useNavigate();
return (
<MobileEdgeBackHandler
onBack={() => navigate(-1)}
canStart={() => window.history.length > 1}
/>
);
}The handler listens for left-edge touch swipes, prevents native horizontal scroll when the gesture clearly becomes a back swipe, triggers haptics when available, and calls onBack only after a committed right swipe.
Route transition
import { MobileRouteTransition } from "@splitin/react-mobile-interactions";
export function MobilePage() {
return (
<MobileRouteTransition transitionKey="settings" direction="up">
<main>...</main>
</MobileRouteTransition>
);
}The default transition is a bottom sheet-up motion using nativeSprings.smooth. Set active={false} to render children directly.
Motion presets
import {
nativeDialogVariants,
nativeOverlayVariants,
nativeSheetVariants,
nativeSprings,
shouldAnimate,
} from "@splitin/react-mobile-interactions";shouldAnimate() reads prefers-reduced-motion at runtime. Components should call it when deciding whether to apply motion-sensitive transforms.
Careers
This folder is product-tech an individual contributor owns end to end. SplitIn engineering: Tech 51%. Business 50. We invest 45% as R&D back to the open-source tools SplitIn uses. MVP first, then merge with tests, trust-me-bro benchmarks, and live user tests. We build fast and then refine — every contributor brings an acquaintance after building the building blocks of the architecture. Every project would be allotted funds. Full mission and stack: splitin.net/tech-stack. Hub: open-internal-tools.
Own this package end to end — or explore SplitIn tech careers — at https://www.splitin.net/careers-requests.
License
MIT. Free to use in personal, open-source, and commercial applications. See LICENSE. Program rules: CONTRIBUTING.md.
