React Hooks

SmoothUI React hooks for responsive design and device detection. useIsMobile hook for mobile breakpoint detection with SSR support and real-time updates.

Last updated: January 30, 2026

Overview

SmoothUI provides utility hooks that help you build responsive, device-aware components. These hooks are designed for performance and work seamlessly with Server-Side Rendering (SSR).


useIsMobile

Detects whether the current viewport is mobile-sized. Returns a boolean that updates in real-time as the viewport changes.

Installation

npx shadcn@latest add @smoothui/use-mobile

Or copy the hook directly:

import * as React from "react";

const MOBILE_BREAKPOINT = 768;

export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
    undefined
  );

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    };
    mql.addEventListener("change", onChange);
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    return () => mql.removeEventListener("change", onChange);
  }, []);

  return !!isMobile;
}

Usage

import { useIsMobile } from "@/hooks/use-mobile";

function ResponsiveComponent() {
  const isMobile = useIsMobile();

  return (
    <div>
      {isMobile ? (
        <MobileNavigation />
      ) : (
        <DesktopNavigation />
      )}
    </div>
  );
}

Parameters

This hook takes no parameters. The mobile breakpoint is set to 768px by default.

Returns

ValueTypeDescription
isMobilebooleantrue if viewport width is less than 768px

Features

  • SSR Safe: Returns false during server-side rendering (no hydration mismatch)
  • Real-time Updates: Responds to viewport changes via matchMedia listener
  • Performance Optimized: Uses matchMedia instead of resize events for better performance
  • Memory Safe: Properly cleans up event listeners on unmount

Common Use Cases

Conditional Rendering

function Header() {
  const isMobile = useIsMobile();

  return (
    <header>
      <Logo />
      {isMobile ? <HamburgerMenu /> : <NavigationLinks />}
    </header>
  );
}

Responsive Animations

import { motion } from "motion/react";
import { useIsMobile } from "@/hooks/use-mobile";

function AnimatedCard() {
  const isMobile = useIsMobile();

  return (
    <motion.div
      whileHover={isMobile ? {} : { scale: 1.02 }}
      // Disable hover animations on touch devices
    >
      Card content
    </motion.div>
  );
}

Responsive Grid

function ProductGrid({ products }) {
  const isMobile = useIsMobile();
  const columns = isMobile ? 1 : 3;

  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: `repeat(${columns}, 1fr)`
    }}>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

SSR Considerations

The hook returns false on the initial server render, then updates on the client. If you need to avoid layout shift, consider:

function ResponsiveLayout() {
  const isMobile = useIsMobile();
  const [mounted, setMounted] = React.useState(false);

  React.useEffect(() => {
    setMounted(true);
  }, []);

  // Show a loading state or neutral layout until mounted
  if (!mounted) {
    return <LayoutSkeleton />;
  }

  return isMobile ? <MobileLayout /> : <DesktopLayout />;
}

Customizing the Breakpoint

If you need a different breakpoint, create a modified version:

const TABLET_BREAKPOINT = 1024;

export function useIsTablet() {
  const [isTablet, setIsTablet] = React.useState<boolean | undefined>(
    undefined
  );

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${TABLET_BREAKPOINT - 1}px)`);
    const onChange = () => {
      setIsTablet(window.innerWidth < TABLET_BREAKPOINT);
    };
    mql.addEventListener("change", onChange);
    setIsTablet(window.innerWidth < TABLET_BREAKPOINT);
    return () => mql.removeEventListener("change", onChange);
  }, []);

  return !!isTablet;
}

Best Practices

Prefer CSS for Simple Responsive Layouts

For simple responsive styling, CSS media queries are more performant than JavaScript:

// Prefer CSS when possible
<div className="grid grid-cols-1 md:grid-cols-3">

// Use hooks for complex logic or conditional rendering
const isMobile = useIsMobile();
if (isMobile) return <MobileOnlyFeature />;

Avoid Excessive Re-renders

The hook only triggers re-renders when crossing the breakpoint threshold, not on every resize event.

Test Across Devices

Always test responsive behavior on actual devices, not just browser dev tools resizing.