Mobile optimization for Object UI — responsive hooks, gesture support, touch targets, and PWA utilities.
- 📱 Responsive Hooks -
useBreakpointanduseResponsivefor adaptive layouts - 👆 Gesture Support -
useGestureanduseSpecGesturefor swipe, pinch, and long-press detection - 🔄 Pull to Refresh - Native pull-to-refresh behavior with
usePullToRefresh - 🎯 Touch Targets -
useTouchTargetfor accessible minimum-size touch areas - 📐 Responsive Containers -
ResponsiveContainerfor breakpoint-aware rendering - 🏗️ MobileProvider - Context provider for mobile-aware applications
- 📲 PWA Support - Manifest generation and service worker registration
- ⚙️ Configurable Breakpoints - Customizable breakpoint definitions
npm install @object-ui/mobilePeer Dependencies:
react^18.0.0 || ^19.0.0
import { MobileProvider, useBreakpoint } from '@object-ui/mobile';
function MobileNav() {
return <nav>Mobile navigation</nav>;
}
function DesktopSidebar() {
return <aside>Desktop sidebar</aside>;
}
function MainContent() {
return <main>Main content</main>;
}
function ResponsiveApp() {
const { isMobile, isDesktop } = useBreakpoint();
return (
<div>
{isMobile && <MobileNav />}
{isDesktop && <DesktopSidebar />}
<MainContent />
</div>
);
}
export function App() {
return (
<MobileProvider>
<ResponsiveApp />
</MobileProvider>
);
}Wraps your application with mobile context. Both props are optional: pwa takes a
PWAConfig, offline takes a PWAOfflineConfig.
import { MobileProvider } from '@object-ui/mobile';
function App() {
return <p>Your application</p>;
}
export function Root() {
return (
<MobileProvider>
<App />
</MobileProvider>
);
}Hook for detecting the current breakpoint. The current breakpoint name is
breakpoint — one of xs, sm, md, lg, xl, 2xl:
import { useBreakpoint } from '@object-ui/mobile';
export function BreakpointBadge() {
const { isMobile, isTablet, isDesktop, breakpoint, width } = useBreakpoint();
return (
<span>
{breakpoint} at {width}px — mobile {String(isMobile)}, tablet {String(isTablet)}, desktop{' '}
{String(isDesktop)}
</span>
);
}isAbove(bp) and isBelow(bp) are also returned, for comparisons against a named
breakpoint.
Hook for responsive values based on screen size. The keys are breakpoint names; a breakpoint with no entry falls back to the next smaller one that has one:
import { useResponsive } from '@object-ui/mobile';
export function ResponsiveGrid() {
const columns = useResponsive({ xs: 1, md: 2, lg: 4 });
return <div data-columns={columns}>{columns} column(s)</div>;
}useGesture detects one gesture from Object UI's direction-fused vocabulary
(tap, double-tap, long-press, swipe-left, swipe-right, swipe-up,
swipe-down, pinch, rotate, pan) per call, and returns a ref to attach:
import { useGesture } from '@object-ui/mobile';
function navigateNext() {}
function navigateBack() {}
export function SwipeArea() {
const nextRef = useGesture<HTMLDivElement>({
type: 'swipe-left',
onGesture: () => navigateNext(),
});
const backRef = useGesture<HTMLDivElement>({
type: 'swipe-right',
onGesture: () => navigateBack(),
});
return (
<div>
<div ref={nextRef}>Swipe left for the next record</div>
<div ref={backRef}>Swipe right to go back</div>
</div>
);
}useSpecGesture takes the declarative SpecGestureConfig tuning shape instead, and
dispatches to per-gesture callbacks:
import { useSpecGesture } from '@object-ui/mobile';
function handleZoom(scale: number) {
return scale;
}
export function PinchArea() {
const ref = useSpecGesture<HTMLDivElement>({
config: { type: 'pinch', enabled: true, pinch: { minScale: 0.5, maxScale: 3 } },
onPinch: (scale) => handleZoom(scale),
});
return <div ref={ref}>Pinchable content</div>;
}Hook for pull-to-refresh behavior. Attach the returned ref to the scrollable
container:
import { usePullToRefresh } from '@object-ui/mobile';
async function fetchData(): Promise<void> {}
export function Feed() {
const { ref, isRefreshing, pullDistance } = usePullToRefresh<HTMLDivElement>({
onRefresh: async () => await fetchData(),
});
return (
<div ref={ref} data-pull-distance={pullDistance}>
{isRefreshing ? 'Refreshing…' : 'Pull to refresh'}
</div>
);
}The element does not have to exist on the first render. A component that shows
a loading screen first and attaches ref once its data arrives works, and so
does one that later swaps in a new element. The hook binds its touch listeners
to whatever element ref points at after each render.
Pull hosts can nest, for example a list view that renders a grid view inside
it. One pull belongs to the outermost armed host: an inner host lets the
gesture go when an armed pull host sits above it in the DOM, so a single pull
draws one indicator and runs one onRefresh.
Hook for ensuring minimum touch target sizes. It returns the style and className
to spread onto the element; the defaults follow WCAG 2.5.5 (44×44 CSS pixels):
import { useTouchTarget } from '@object-ui/mobile';
export function TapButton() {
const { style, className } = useTouchTarget({
config: { minWidth: 44, minHeight: 44 },
});
return (
<button style={style} className={className}>
Tap me
</button>
);
}Renders children based on breakpoint. Pick the range with minBreakpoint /
maxBreakpoint, or name the breakpoints outright with showOn / hideOn:
import { ResponsiveContainer } from '@object-ui/mobile';
function MobileView() {
return <p>Compact layout</p>;
}
function DesktopView() {
return <p>Full layout</p>;
}
export function BreakpointSwitch() {
return (
<div>
<ResponsiveContainer maxBreakpoint="md">
<MobileView />
</ResponsiveContainer>
<ResponsiveContainer minBreakpoint="lg">
<DesktopView />
</ResponsiveContainer>
</div>
);
}PWAConfig requires enabled, name and shortName. registerServiceWorker takes
the script url and scope plus lifecycle callbacks — the caching strategies live in
the generated worker (getServiceWorkerSource), not in this call:
import { generatePWAManifest, registerServiceWorker } from '@object-ui/mobile';
export const manifest = generatePWAManifest({
enabled: true,
name: 'My App',
shortName: 'My App',
themeColor: '#000',
});
void registerServiceWorker({ url: '/service-worker.js' });- 📦 npm package
- 📝 Changelog
- 🐛 Report an issue
- 🤝 Contributing Guide
- 🗺️ Roadmap
MIT — see LICENSE.