Open Hooks
Device and Browser

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.

Status: stable
Version: 2.0.0
Bundle Size: ~1.0kb
Requires: React 16.8+

useMobile Demo

Detected DeviceDesktop
MobileNoTabletNoDesktopYesLargeNo

Resize your browser to see device type update dynamically.
Mobile: <768px | Tablet: 768–1024px | Desktop: 1024–1440px | Large: >1440px

Installation

npx open-hook add useMobile
yarn dlx open-hook add useMobile
pnpm dlx open-hook add useMobile

API Reference

Parameters

PropTypeDefault
options.desktopBreakpoint?
number
1440
options.tabletBreakpoint?
number
1024
options.mobileBreakpoint?
number
768
options?
UseMobileOptions
{ mobileBreakpoint: 768, tabletBreakpoint: 1024, desktopBreakpoint: 1440 }

Returns

PropTypeDefault
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

MetricValueDescription
Bundle Size~1.0kbMinified + gzipped
Detection Speed< 1msInitial device detection
Memory UsageMinimalSingle state object
Event Listeners2resize + orientationchange

Browser Compatibility

BrowserSupportedNotes
Chrome 4+YesFull support
Firefox 3.5+YesFull support
Safari 3.1+YesFull support
Edge 12+YesFull support
IE 9+YesLimited touch detection
Mobile SafariYesExcellent mobile support
Chrome MobileYesExcellent 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:

  1. Screen width analysis using configurable breakpoints
  2. User agent parsing for platform-specific detection
  3. Touch capability detection using multiple browser APIs
  4. Orientation tracking based on viewport dimensions
  5. 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

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