Skip to main content
The useIsMobile hook provides a simple way to detect whether the current viewport is mobile-sized (width < 640px), with automatic updates when the window is resized.

Signature

function useIsMobile(): boolean

Returns

isMobile
boolean
true if the viewport width is less than 640px, false otherwise

Implementation

src/hooks/useIsMobile.ts
import { useState, useEffect } from 'react'

export const useIsMobile = (): boolean => {
  const [isMobile, setIsMobile] = useState<boolean>(false)

  useEffect(() => {
    const check = () => setIsMobile(window.innerWidth < 640)
    check()
    window.addEventListener('resize', check)
    return () => window.removeEventListener('resize', check)
  }, [])

  return isMobile
}

How it works

  1. Initial state: Starts with false to prevent hydration mismatches
  2. Effect runs: After mount, checks the current window width
  3. Event listener: Attaches resize listener for responsive updates
  4. Cleanup: Removes listener on unmount to prevent memory leaks
The initial state is false to match the server-rendered state. The actual check happens client-side after hydration.

Usage

Basic responsive behavior

import { useIsMobile } from '@/hooks'

function ResponsiveComponent() {
  const isMobile = useIsMobile()

  return (
    <div>
      {isMobile ? (
        <MobileView />
      ) : (
        <DesktopView />
      )}
    </div>
  )
}

Conditional rendering

import { useIsMobile } from '@/hooks'

function Navigation() {
  const isMobile = useIsMobile()

  return (
    <nav>
      {isMobile ? (
        <HamburgerMenu />
      ) : (
        <FullNavigation />
      )}
    </nav>
  )
}

Adjust layout based on screen size

import { useIsMobile } from '@/hooks'

function Gallery() {
  const isMobile = useIsMobile()
  
  const columns = isMobile ? 2 : 4
  const imageSize = isMobile ? 'small' : 'large'

  return (
    <div className={`grid grid-cols-${columns}`}>
      {items.map(item => (
        <Image key={item.id} size={imageSize} />
      ))}
    </div>
  )
}

Breakpoint

The hook uses 640px as the mobile breakpoint, which corresponds to Tailwind CSS’s sm breakpoint.
BreakpointWidthClassification
< 640pxMobileisMobile = true
≥ 640pxDesktopisMobile = false
If you need custom breakpoints, consider creating a variant like useBreakpoint(width) or use CSS media queries with Tailwind’s responsive classes.

Performance considerations

Debouncing resize events

For performance-critical applications, consider debouncing the resize handler:
import { useIsMobile } from '@/hooks'
import { useDebounce } from '@/hooks'

function PerformantComponent() {
  const isMobileRaw = useIsMobile()
  const isMobile = useDebounce(isMobileRaw, 150)
  
  // Now resize events are debounced by 150ms
  return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>
}

SSR considerations

The hook initializes to false on the server to avoid hydration mismatches. The actual value is computed client-side after mount.
function SafeComponent() {
  const isMobile = useIsMobile()
  const [hydrated, setHydrated] = useState(false)

  useEffect(() => setHydrated(true), [])

  if (!hydrated) {
    return <Skeleton />
  }

  return isMobile ? <MobileView /> : <DesktopView />
}

useDebounce

Debounce rapidly changing values

useHydrated

Detect client-side hydration completion

Build docs developers (and LLMs) love