useIsMobile
Detect whether the current viewport is “mobile” (by default, width < 768px) using a media query, and adapt your UI behavior accordingly.
useIsMobile
useIsMobile returns a boolean that indicates whether the current viewport width is below the mobile breakpoint (defaults to 768px). It listens for viewport size changes using the matchMedia API and updates reactively.
true→ viewport is narrower than 768pxfalse→ viewport is 768px or wider
This hook is a small convenience around window.matchMedia. It’s ideal for toggling UI or behavior between compact and desktop layouts without wiring up media listeners manually.
API
function useIsMobile(): boolean;Prop
Type
Usage
1) Conditional rendering (mobile vs. desktop navigation)
import * as React from 'react';
import { useIsMobile } from '@loke/design-system/use-mobile';
function AppHeader() {
const isMobile = useIsMobile();
return (
<header className="border-b bg-card">
{isMobile ? <MobileNav /> : <DesktopNav />}
</header>
);
}2) Adapting behavior (e.g., default open state)
import * as React from 'react';
import { useIsMobile } from '@loke/design-system/use-mobile';
export function SidebarContainer() {
const isMobile = useIsMobile();
// Sidebar closed by default on mobile; open on desktop
const [open, setOpen] = React.useState(!isMobile);
React.useEffect(() => {
setOpen(!isMobile);
}, [isMobile]);
return (
<aside className={open ? 'block' : 'hidden md:block'}>
<Sidebar />
</aside>
);
}3) Show compact controls for small viewports
import { Button } from '@loke/design-system/button';
import { useIsMobile } from '@loke/design-system/use-mobile';
function ActionBar() {
const isMobile = useIsMobile();
return (
<div className="flex items-center gap-2">
<Button size={isMobile ? 'sm' : 'md'}>Primary</Button>
<Button size={isMobile ? 'sm' : 'md'} variant="outline">
Secondary
</Button>
</div>
);
}SSR and first-render behavior
- This hook uses browser APIs (
matchMedia,window.innerWidth) and should run in Client Components (e.g., with"use client"in Next.js). - Internally, it initializes to
undefinedand then sets the boolean after mounting; the returned value is coerced tofalseuntil the first effect runs.- If avoiding a brief “desktop” render on mobile is important, gate the UI until the first measurement completes or render layout styles responsively with CSS and use the hook only for behavior.
Example: wait until hydrated value is known
function ResponsivePanel() {
const isMobile = useIsMobile();
const [ready, setReady] = React.useState(false);
React.useEffect(() => setReady(true), []);
if (!ready) return null;
return isMobile ? <MobilePanel /> : <DesktopPanel />;
}For purely visual layout differences, prefer CSS media queries (e.g., Tailwind responsive classes). Use this hook when your logic/behavior depends on viewport size.
Custom breakpoint (optional pattern)
The built-in hook uses a fixed 768px breakpoint. If you need a different threshold, you can build a small wrapper:
import * as React from 'react';
function useMaxWidth(maxWidthPx: number) {
const [match, setMatch] = React.useState<boolean | undefined>(undefined);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${maxWidthPx - 1}px)`);
const onChange = () => setMatch(window.innerWidth < maxWidthPx);
mql.addEventListener('change', onChange);
onChange(); // initialize
return () => mql.removeEventListener('change', onChange);
}, [maxWidthPx]);
return !!match;
}
// Example:
function Toolbar() {
const compact = useMaxWidth(1024); // treat <1024px as compact
return <div className={compact ? 'gap-1' : 'gap-3'}>…</div>;
}Tips
- Prefer this hook for behavioral toggles (state defaults, feature flags) and use CSS for visual layout changes whenever possible.
- Combine with other responsive utilities (e.g.,
Columns,Stack, or Tailwind’ssm:,md:,lg:modifiers) for a clean, scalable approach. - If you need to support server-only rendering for a specific code path, guard
window/documentaccess or defer rendering until mounted.
Import
import { useIsMobile } from '@loke/design-system/use-mobile';