useMobile
Detect mobile devices and screen sizes with responsive breakpoints in React applications.
useMobile
Detect mobile devices and screen sizes with responsive breakpoints in React applications.
useMobile Demo
Resize your browser to see device type update dynamically.
Mobile: <768px | Tablet: 768–1024px | Desktop: 1024–1440px | Large: >1440px
Installation
npx open-hook add useMobileyarn dlx open-hook add useMobilepnpm dlx open-hook add useMobileAPI Reference
Parameters
| Prop | Type | Default |
|---|---|---|
options.desktopBreakpoint? | number | 1440 |
options.tabletBreakpoint? | number | 1024 |
options.mobileBreakpoint? | number | 768 |
options? | UseMobileOptions | { mobileBreakpoint: 768, tabletBreakpoint: 1024, desktopBreakpoint: 1440 } |
Returns
| Prop | Type | Default |
|---|---|---|
viewport? | { width: number; height: number } | - |
userAgent? | UserAgentInfo | - |
touchScreen? | boolean | - |
orientation? | 'portrait' | 'landscape' | - |
screenSize? | 'mobile' | 'tablet' | 'desktop' | 'large' | - |
isLarge? | boolean | - |
isDesktop? | boolean | - |
isTablet? | boolean | - |
isMobile? | boolean | - |
TypeScript Signature
type ScreenSize = "mobile" | "tablet" | "desktop" | "large";
type Orientation = "portrait" | "landscape";
interface DeviceInfo {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
isLarge: boolean;
screenSize: ScreenSize;
orientation: Orientation;
touchScreen: boolean;
userAgent: {
isIOS: boolean;
isAndroid: boolean;
isSafari: boolean;
isChrome: boolean;
isFirefox: boolean;
isEdge: boolean;
};
viewport: {
width: number;
height: number;
};
}
interface UseMobileOptions {
mobileBreakpoint?: number;
tabletBreakpoint?: number;
desktopBreakpoint?: number;
}
function useMobile(options?: UseMobileOptions): DeviceInfo;Advanced Usage Examples
function ResponsiveNavigation() {
const { isMobile, isTablet, screenSize, orientation } = useMobile();
const getLayoutConfig = () => {
switch (screenSize) {
case "mobile":
return {
showSidebar: false,
itemsPerRow: 1,
showSearch: false,
navigationStyle: "bottom-tabs",
};
case "tablet":
return {
showSidebar: orientation === "landscape",
itemsPerRow: orientation === "portrait" ? 2 : 3,
showSearch: true,
navigationStyle: "top-bar",
};
case "desktop":
case "large":
return {
showSidebar: true,
itemsPerRow: 4,
showSearch: true,
navigationStyle: "sidebar",
};
}
};
const layout = getLayoutConfig();
return (
<div className={`app-layout ${screenSize}`}>
{layout.showSidebar && (
<aside className="sidebar">
<NavigationSidebar />
</aside>
)}
<main className="main-content">
{layout.showSearch && (
<header className="search-header">
<SearchBar expanded={!isMobile} />
</header>
)}
<div
className="content-grid"
style={{
gridTemplateColumns: `repeat(${layout.itemsPerRow}, 1fr)`,
}}
>
<ContentItems />
</div>
</main>
{layout.navigationStyle === "bottom-tabs" && (
<nav className="bottom-navigation">
<BottomTabs />
</nav>
)}
</div>
);
}function InteractiveComponent() {
const { touchScreen, isMobile, userAgent } = useMobile();
const [interactionMode, setInteractionMode] = useState("auto");
// Adapt interaction patterns based on device
const getInteractionProps = () => {
if (touchScreen) {
return {
// Touch-friendly props
onTouchStart: handleTouchStart,
onTouchMove: handleTouchMove,
onTouchEnd: handleTouchEnd,
// Larger touch targets
className: "touch-target-large",
// Prevent hover states on touch devices
style: { "--hover-enabled": "none" },
};
} else {
return {
// Mouse interaction props
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onClick: handleClick,
onDoubleClick: handleDoubleClick,
className: "hover-enabled",
};
}
};
const handleGesture = (gesture: string) => {
if (touchScreen && isMobile) {
switch (gesture) {
case "swipe-left":
navigateNext();
break;
case "swipe-right":
navigatePrevious();
break;
case "pinch-zoom":
toggleZoom();
break;
case "long-press":
showContextMenu();
break;
}
}
};
return (
<div className="interactive-component">
<div {...getInteractionProps()}>
<h3>Interactive Content</h3>
{touchScreen ? (
<div className="touch-instructions">
<p>👆 Touch interactions available:</p>
<ul>
<li>Swipe left/right to navigate</li>
<li>Pinch to zoom</li>
<li>Long press for context menu</li>
</ul>
</div>
) : (
<div className="mouse-instructions">
<p>🖱️ Mouse interactions available:</p>
<ul>
<li>Hover for preview</li>
<li>Click to select</li>
<li>Double-click to open</li>
</ul>
</div>
)}
{/* iOS-specific features */}
{userAgent.isIOS && (
<div className="ios-features">
<button onClick={() => addToHomeScreen()}>
📱 Add to Home Screen
</button>
</div>
)}
{/* Android-specific features */}
{userAgent.isAndroid && (
<div className="android-features">
<button onClick={() => requestInstallPrompt()}>
📲 Install App
</button>
</div>
)}
</div>
</div>
);
}function PlatformOptimizedApp() {
const { userAgent, isMobile, touchScreen, viewport } = useMobile();
const getPlatformFeatures = () => {
const features = {
notifications: "Notification" in window,
geolocation: "geolocation" in navigator,
camera: "mediaDevices" in navigator,
fullscreen: "requestFullscreen" in document.documentElement,
sharing: "share" in navigator,
orientation: "orientation" in screen,
vibration: "vibrate" in navigator,
};
return features;
};
const features = getPlatformFeatures();
const optimizeForPlatform = () => {
// iOS-specific optimizations
if (userAgent.isIOS) {
// Handle iOS viewport quirks
if (viewport.height < 500) {
document.documentElement.style.setProperty(
"--vh",
`${viewport.height * 0.01}px`
);
}
// iOS Safari bounce prevention
document.body.style.overscrollBehavior = "none";
}
// Android-specific optimizations
if (userAgent.isAndroid) {
// Android keyboard handling
if (isMobile) {
const originalHeight = viewport.height;
const onResize = () => {
const currentHeight = window.innerHeight;
const keyboardHeight = originalHeight - currentHeight;
if (keyboardHeight > 150) {
document.body.classList.add("keyboard-open");
} else {
document.body.classList.remove("keyboard-open");
}
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}
}
// Safari-specific optimizations
if (userAgent.isSafari) {
// Handle Safari's aggressive memory management
const images = document.querySelectorAll("img");
images.forEach((img) => {
img.loading = "lazy";
});
}
};
useEffect(() => {
const cleanup = optimizeForPlatform();
return cleanup;
}, [userAgent, viewport]);
return (
<div className="platform-optimized-app">
<header>
<h1>Platform Features</h1>
<div className="platform-info">
<span className="platform-badge">
{userAgent.isIOS && "🍎 iOS"}
{userAgent.isAndroid && "🤖 Android"}
{!userAgent.isIOS && !userAgent.isAndroid && "💻 Desktop"}
</span>
</div>
</header>
<main>
<section className="feature-grid">
{features.notifications && (
<FeatureCard
icon="🔔"
title="Push Notifications"
available={features.notifications}
onEnable={() => requestNotificationPermission()}
/>
)}
{features.geolocation && (
<FeatureCard
icon="📍"
title="Location Services"
available={features.geolocation}
onEnable={() => getCurrentLocation()}
/>
)}
{features.camera && isMobile && (
<FeatureCard
icon="📷"
title="Camera Access"
available={features.camera}
onEnable={() => accessCamera()}
/>
)}
{features.sharing && (
<FeatureCard
icon="📤"
title="Native Sharing"
available={features.sharing}
onEnable={() => shareContent()}
/>
)}
{features.vibration && touchScreen && (
<FeatureCard
icon="📳"
title="Haptic Feedback"
available={features.vibration}
onEnable={() => navigator.vibrate(200)}
/>
)}
</section>
{/* Platform-specific UI elements */}
{userAgent.isIOS && (
<div className="ios-ui">
<button className="ios-button">iOS Style Button</button>
</div>
)}
{userAgent.isAndroid && (
<div className="material-ui">
<button className="material-button">Material Button</button>
</div>
)}
</main>
</div>
);
}function PerformanceOptimizedComponent() {
const { isMobile, isTablet, viewport, screenSize } = useMobile();
// Adjust performance based on device capabilities
const getPerformanceConfig = () => {
const baseConfig = {
imageQuality: 90,
animationDuration: 300,
maxConcurrentRequests: 6,
lazyLoadThreshold: 100,
prefetchCount: 3,
};
if (isMobile) {
return {
...baseConfig,
imageQuality: 70, // Lower quality for mobile
animationDuration: 200, // Faster animations
maxConcurrentRequests: 3, // Fewer concurrent requests
lazyLoadThreshold: 50, // More aggressive lazy loading
prefetchCount: 1, // Less prefetching
};
}
if (isTablet) {
return {
...baseConfig,
imageQuality: 80,
maxConcurrentRequests: 4,
prefetchCount: 2,
};
}
return baseConfig;
};
const config = getPerformanceConfig();
// Responsive image loading
const getImageSrc = (baseUrl: string, size: string) => {
const sizeMap = {
mobile: "small",
tablet: "medium",
desktop: "large",
large: "xlarge",
};
const targetSize = sizeMap[screenSize];
return `${baseUrl}?size=${targetSize}&quality=${config.imageQuality}`;
};
// Conditional feature loading
const shouldLoadFeature = (feature: string) => {
const heavyFeatures = ["3d-animations", "video-background", "particles"];
if (isMobile && heavyFeatures.includes(feature)) {
return false; // Skip heavy features on mobile
}
return true;
};
// Memory-aware component rendering
const getComponentLimit = () => {
if (isMobile) return 10;
if (isTablet) return 20;
return 50;
};
return (
<div className="performance-optimized">
<div className="content-grid">
{/* Responsive images */}
<img
src={getImageSrc("/api/image/hero", "large")}
alt="Hero image"
loading="lazy"
style={{
maxWidth: "100%",
height: "auto",
}}
/>
{/* Conditional heavy components */}
{shouldLoadFeature("3d-animations") && (
<Suspense fallback={<div>Loading 3D content...</div>}>
<Heavy3DComponent />
</Suspense>
)}
{shouldLoadFeature("video-background") ? (
<VideoBackground quality={isMobile ? "low" : "high"} />
) : (
<StaticBackground />
)}
{/* Performance metrics display */}
<div className="performance-info">
<h3>Performance Configuration</h3>
<ul>
<li>Device: {screenSize}</li>
<li>Image Quality: {config.imageQuality}%</li>
<li>Animation Duration: {config.animationDuration}ms</li>
<li>Max Requests: {config.maxConcurrentRequests}</li>
<li>Component Limit: {getComponentLimit()}</li>
</ul>
</div>
</div>
</div>
);
}
// Example of memory-efficient list rendering
function OptimizedList({ items }: { items: any[] }) {
const { isMobile } = useMobile();
const [visibleItems, setVisibleItems] = useState(isMobile ? 10 : 50);
const loadMore = () => {
setVisibleItems((prev) => prev + (isMobile ? 10 : 20));
};
return (
<div className="optimized-list">
{items.slice(0, visibleItems).map((item, index) => (
<ListItem key={item.id} item={item} />
))}
{visibleItems < items.length && (
<button onClick={loadMore} className="load-more">
Load More ({items.length - visibleItems} remaining)
</button>
)}
</div>
);
}Best Practices
Use device detection to enhance user experience, not restrict it:
const { isMobile, touchScreen, userAgent } = useMobile();
// ✅ Good: Progressive enhancement
const Component = () => (
<div>
<BaseFeatures />
{!isMobile && <DesktopEnhancements />}
{touchScreen && <TouchGestures />}
{userAgent.isIOS && <iOSOptimizations />}
</div>
);
// ❌ Avoid: Feature blocking
const BadComponent = () => (
<div>
{isMobile ? <p>Feature not available on mobile</p> : <FullFeatureSet />}
</div>
);Responsive Design Guidelines
- 📱 Mobile First: Design for mobile, enhance for larger screens
- 🎯 Touch Targets: Use appropriate touch target sizes (44px minimum)
- ⚡ Performance: Optimize images and animations for mobile devices
- 🔄 Orientation: Handle orientation changes gracefully
Do's and Don'ts
- ✅ Use device detection for progressive enhancement
- ✅ Optimize performance based on device capabilities
- ✅ Provide appropriate touch targets and interactions
- ✅ Test on real devices, not just browser dev tools
- ❌ Don't block features completely on mobile
- ❌ Avoid device-specific code without fallbacks
- ❌ Don't assume mobile means slow internet
- ❌ Avoid hard-coded breakpoints without customization
Performance Metrics
| Metric | Value | Description |
|---|---|---|
| Bundle Size | ~1.0kb | Minified + gzipped |
| Detection Speed | < 1ms | Initial device detection |
| Memory Usage | Minimal | Single state object |
| Event Listeners | 2 | resize + orientationchange |
Browser Compatibility
| Browser | Supported | Notes |
|---|---|---|
| Chrome 4+ | ✅Yes | Full support |
| Firefox 3.5+ | ✅Yes | Full support |
| Safari 3.1+ | ✅Yes | Full support |
| Edge 12+ | ✅Yes | Full support |
| IE 9+ | ✅Yes | Limited touch detection |
| Mobile Safari | ✅Yes | Excellent mobile support |
| Chrome Mobile | ✅Yes | Excellent mobile support |
Use Cases
Responsive Design
Adapt layouts and features based on screen size and device capabilities.
Touch Optimization
Provide appropriate touch targets and gestures for touch-enabled devices.
Performance Tuning
Optimize application performance based on device constraints and capabilities.
Platform Features
Enable platform-specific features and optimizations for better user experience.
Accessibility
Accessibility Considerations
- Ensure touch targets are at least 44px for mobile accessibility - Provide keyboard navigation alternatives for touch gestures - Use appropriate font sizes and contrast ratios for all screen sizes - Test with screen readers on different devices - Consider reduced motion preferences for animations
Troubleshooting
Internals
How It Works
The hook combines multiple detection methods:
- Screen width analysis using configurable breakpoints
- User agent parsing for platform-specific detection
- Touch capability detection using multiple browser APIs
- Orientation tracking based on viewport dimensions
- Real-time updates via resize and orientation change events
The implementation prioritizes accuracy and performance while providing comprehensive device information.
Testing Example
import { renderHook, act } from "@testing-library/react";
import { useMobile } from "./useMobile";
// Mock window properties
const mockWindow = (width: number, height: number, userAgent: string) => {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: width,
});
Object.defineProperty(window, "innerHeight", {
writable: true,
configurable: true,
value: height,
});
Object.defineProperty(navigator, "userAgent", {
writable: true,
configurable: true,
value: userAgent,
});
};
describe("useMobile", () => {
beforeEach(() => {
// Reset to default desktop state
mockWindow(1024, 768, "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
});
it("should detect mobile devices", () => {
mockWindow(320, 568, "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)");
const { result } = renderHook(() => useMobile());
expect(result.current.isMobile).toBe(true);
expect(result.current.isTablet).toBe(false);
expect(result.current.isDesktop).toBe(false);
expect(result.current.screenSize).toBe("mobile");
expect(result.current.userAgent.isIOS).toBe(true);
});
it("should detect tablet devices", () => {
mockWindow(768, 1024, "Mozilla/5.0 (iPad; CPU OS 14_0)");
const { result } = renderHook(() => useMobile());
expect(result.current.isMobile).toBe(false);
expect(result.current.isTablet).toBe(true);
expect(result.current.isDesktop).toBe(false);
expect(result.current.screenSize).toBe("tablet");
});
it("should detect desktop devices", () => {
mockWindow(1200, 800, "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
const { result } = renderHook(() => useMobile());
expect(result.current.isMobile).toBe(false);
expect(result.current.isTablet).toBe(false);
expect(result.current.isDesktop).toBe(true);
expect(result.current.screenSize).toBe("desktop");
});
it("should update on window resize", () => {
const { result } = renderHook(() => useMobile());
expect(result.current.isMobile).toBe(false);
act(() => {
mockWindow(320, 568, navigator.userAgent);
window.dispatchEvent(new Event("resize"));
});
expect(result.current.isMobile).toBe(true);
});
it("should detect orientation changes", () => {
mockWindow(320, 568, "Mobile User Agent");
const { result } = renderHook(() => useMobile());
expect(result.current.orientation).toBe("portrait");
act(() => {
mockWindow(568, 320, "Mobile User Agent");
window.dispatchEvent(new Event("orientationchange"));
});
expect(result.current.orientation).toBe("landscape");
});
it("should accept custom breakpoints", () => {
mockWindow(800, 600, "Desktop User Agent");
const { result } = renderHook(() =>
useMobile({
mobileBreakpoint: 600,
tabletBreakpoint: 900,
})
);
expect(result.current.isTablet).toBe(true);
expect(result.current.isMobile).toBe(false);
});
});FAQ
Related Hooks
- useLocation - Browser navigation and URL management
- useLocalStorage - Store device preferences
- useClickOutside - Handle touch interactions
Changelog
- 2.0.0 — Enhanced TypeScript support, comprehensive user agent detection, improved performance, orientation tracking, custom breakpoints
- 1.3.0 — Added viewport dimensions and orientation detection
- 1.2.0 — Improved user agent parsing and touch detection
- 1.1.0 — Added custom breakpoint support
- 1.0.0 — Initial release with basic mobile detection