Skip to content

React Integration ​

Import from responsive-media/react.

ts
import {
  useResponsive,
  useBreakpoints,
  useMediaQuery,
  useContainerState,
} from 'responsive-media/react'

Typed hooks from your config ​

defineResponsive(config, options?)

Applies config to the shared state and returns useResponsive, useBreakpoints and useResponsiveValue, with the state and the breakpoint keys inferred from config — no generic to write by hand. Put the call in one module and import the hooks from there.

ts
// responsive.ts
import { defineResponsive } from 'responsive-media/react'

export const { useResponsive, useBreakpoints } = defineResponsive(
  {
    mobile: [{ type: 'max-width', value: 600 }],
    smallTablet: [{ type: 'max-width', value: 850 }],
    desktop: [{ type: 'min-width', value: 961 }],
  },
  { order: ['mobile', 'smallTablet', 'desktop'] },
)
  • useResponsive() returns { mobile: boolean; smallTablet: boolean; desktop: boolean }.
  • useBreakpoints() accepts only those keys: isAbove('nope') is a compile error, and current is 'mobile' | 'smallTablet' | 'desktop' | null.

The call replaces the configuration an earlier setResponsiveConfig() set, because they share one state.

Responsive State ​

useResponsive<T>()

Returns the current responsive state. Re-renders only when state changes. Uses useSyncExternalStore internally. Accepts a generic type parameter to narrow the returned state — see ConfigToState<T>.

tsx
import { useResponsive } from 'responsive-media/react'

type MyState = { sm: boolean; lg: boolean }

function App() {
  const { sm, lg } = useResponsive<MyState>()
  return sm ? <MobileNav /> : <DesktopNav />
}

Breakpoint Helpers ​

useBreakpoints()

Returns ordered breakpoint helpers. Re-renders when the responsive state changes.

tsx
import { useBreakpoints } from 'responsive-media/react'

function Nav() {
  const { current, isAbove, isBelow, between } = useBreakpoints()
  return (
    <>
      <span>Current: {current}</span>
      {isAbove('sm') ? <DesktopNav /> : <MobileNav />}
      {between('sm', 'lg') && <TabletBanner />}
    </>
  )
}

Unlike Vue, current is a plain string | null (not a ref). Re-renders are triggered by useSyncExternalStore.

Media Query ​

useMediaQuery(query)

boolean

Tracks a raw CSS media query string. SSR-safe (returns false on the server).

tsx
import { useMediaQuery } from 'responsive-media/react'

function ThemeToggle() {
  const isDark = useMediaQuery('(prefers-color-scheme: dark)')
  const canHover = useMediaQuery('(hover: hover)')
  return <button className={isDark ? 'dark' : 'light'}>Toggle</button>
}

Container State ​

useContainerState(ref, config, options?)

Tracks an element's dimensions and returns a state object whose keys are inferred from config. Sets up and tears down ResizeObserver via useEffect.

tsx
import { useRef } from 'react'
import { useContainerState } from 'responsive-media/react'

function Card() {
  const ref = useRef<HTMLDivElement>(null)
  const { compact, wide } = useContainerState(ref, {
    compact: [{ type: 'max-width', value: 300 }],
    wide: [{ type: 'min-width', value: 600 }],
  })

  return (
    <div ref={ref}>{compact ? <CompactLayout /> : wide ? <WideLayout /> : <DefaultLayout />}</div>
  )
}

config and options are treated as static after mount. Wrap in useMemo if they change.

If the target element isn't attached yet when this hook first runs (e.g. it's behind a later-mounted conditional or a portal), it doesn't give up — a bounded requestAnimationFrame retry loop keeps checking the ref until the element resolves, then sets up the ResizeObserver.

Responsive Values ​

useResponsiveValue(map, fallback?)

T | undefined

Returns the value of the first active breakpoint in map, or fallback when none is active. The order of map decides: the first key that is true wins, so when breakpoints overlap, list the narrowest one first.

tsx
import { useResponsiveValue } from 'responsive-media/react'

function Gallery() {
  const columns = useResponsiveValue({ mobile: 1, tablet: 2, desktop: 4 })
  return <Grid columns={columns} />
}

With the hooks from defineResponsive() the keys of map are checked against your config.

User Preferences ​

useUserPreferences()

{ dark, light, reducedMotion, highContrast, lowContrast, noHover, coarsePointer, forcedColors, print }

The user's appearance and accessibility preferences — the AccessibilityPreset as ready state, with nothing to configure. It reads a state of its own, so it never interferes with your breakpoints. Every key is false on the server.

tsx
import { useUserPreferences } from 'responsive-media/react'

function Hero() {
  const { reducedMotion } = useUserPreferences()
  return reducedMotion ? <StillImage /> : <Parallax />
}

Viewport Size ​

useViewportSize(options?)

{ width: number; height: number }

The window size in pixels, for the moments you need the number and not a breakpoint. It is 0 by 0 on the server, and the object keeps its identity between renders until the size changes.

  • throttle — number · default: 100. Milliseconds between updates while the window is resized; 0 updates on every event.

Hydration ​

While React hydrates, the hooks return the server snapshot (getSsrState()), so the first client render matches the server HTML and React then re-renders with the real value without a recoverable error. Set ssrState to choose what the server renders — see SSR & Hydration.