Skip to content

Core Concepts ​

Config format ​

Each breakpoint is described by a MediaQueryConfig — an array of conditions combined with AND, or a nested array of groups combined with OR.

AND (flat array) ​

ts
// (min-width: 768px) and (max-width: 1023px)
;[
  { type: 'min-width', value: 768 },
  { type: 'max-width', value: 1023 },
]

OR (nested array) ​

ts
// (max-width: 600px), (orientation: portrait) and (max-width: 1024px)
;[
  [{ type: 'max-width', value: 600 }],
  [
    { type: 'orientation', value: 'portrait' },
    { type: 'max-width', value: 1024 },
  ],
]

Raw media type ​

Use type: 'raw' to insert a value verbatim — useful for media types like print or screen:

ts
// Matches 'print' media type
;[{ type: 'raw', value: 'print' }][
  // screen and (max-width: 600px)
  ({ type: 'raw', value: 'screen' }, { type: 'max-width', value: 600 })
]

Supported condition types ​

typeExample valueGenerated query
min-width768(min-width: 768px)
max-width1023(max-width: 1023px)
min-height600(min-height: 600px)
max-height900(max-height: 900px)
orientation'portrait'(orientation: portrait)
aspect-ratio'16/9'(aspect-ratio: 16/9)
prefers-color-scheme'dark'(prefers-color-scheme: dark)
prefers-reduced-motion'reduce'(prefers-reduced-motion: reduce)
prefers-contrast'more'(prefers-contrast: more)
hover'none'(hover: none)
pointer'coarse'(pointer: coarse)
forced-colors'active'(forced-colors: active)
resolution'2dppx'(resolution: 2dppx)
display-mode'standalone'(display-mode: standalone)
raw'print'print (verbatim)

ConfigToState<T> derives a boolean-state type from a config object, so consuming code doesn't have to hand-write the resulting state shape:

ts
import type { ConfigToState, MediaQueryConfig } from 'responsive-media'

const config = {
  sm: [{ type: 'min-width', value: 640 }],
  lg: [{ type: 'min-width', value: 1024 }],
} satisfies Record<string, MediaQueryConfig>

type MyState = ConfigToState<typeof config>
// → { sm: boolean; lg: boolean }

const { sm, lg } = responsiveState.getState<MyState>()

Global singleton ​

The library exports a pre-configured singleton responsiveState initialized with the default ResponsiveConfig (mobile / tablet / desktop).

ts
import { responsiveState, setResponsiveConfig, getResponsiveMediaQueries } from 'responsive-media'

// Re-configure the singleton
setResponsiveConfig(
  {
    sm: [{ type: 'max-width', value: 767 }],
    lg: [{ type: 'min-width', value: 1024 }],
  },
  {
    order: ['sm', 'lg'], // for isAbove / isBelow / between
    debounce: 50, // ms — throttle subscribe() listeners
  },
)

// Read a stable snapshot
const { sm, lg } = responsiveState.getState()

// Live proxy access (never debounced)
console.log(responsiveState.proxy.sm)

// Get the generated CSS strings
const mq = getResponsiveMediaQueries()
// { sm: '(max-width: 767px)', lg: '(min-width: 1024px)' }

Default config (ResponsiveConfig) ​

KeyRange
mobile≤ 600px
tablet601 – 960px
desktop≥ 961px

Creating Independent Instances ​

createResponsiveState()

Creates independent instances — useful for per-request SSR, multiple independent contexts, or testing:

ts
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media'

const layoutState = createResponsiveState(TailwindPreset, {
  order: [...TailwindOrder],
})

const themeState = createResponsiveState({
  dark: [{ type: 'prefers-color-scheme', value: 'dark' }],
  reducedMotion: [{ type: 'prefers-reduced-motion', value: 'reduce' }],
})

layoutState.subscribe((s) => console.log('layout:', s))
themeState.subscribe((s) => console.log('theme:', s))

// Cleanup when done (e.g. per-request SSR)
layoutState.destroy()

ContainerState ​

Tracks an element's dimensions via ResizeObserver and evaluates breakpoint conditions in JavaScript — the same concept as CSS Container Queries, but in JS.

The API is identical to ReactiveResponsiveState — all subscription methods work the same way (see Subscription API and Utilities).

ts
import { createContainerState } from 'responsive-media/container'
// or: import { createContainerState } from 'responsive-media';

const card = document.querySelector('.card')!

const cardState = createContainerState(
  card,
  {
    compact: [{ type: 'max-width', value: 300 }],
    normal: [
      { type: 'min-width', value: 301 },
      { type: 'max-width', value: 599 },
    ],
    wide: [{ type: 'min-width', value: 600 }],
  },
  {
    order: ['compact', 'normal', 'wide'],
  },
)

// Reactive class toggling
cardState.on('compact', (v) => card.classList.toggle('card--compact', v))

// Sync CSS custom properties: --card-compact: 1; --card-wide: 0; …
cardState.syncCSSVars({ prefix: '--card-' })

// Get @container-compatible query strings
const strings = cardState.getMediaQueries()
// { compact: '(max-width: 300px)', wide: '(min-width: 600px)' }

// Cleanup
cardState.destroy()

Supported condition types for ContainerState ​

max-width, min-width, max-height, min-height, orientation, aspect-ratio

SSR / hydration ​

All APIs are SSR-safe — they check for window, matchMedia, and ResizeObserver availability before use and fall back to false on the server.

ScenarioBehaviour
typeof window === 'undefined'All listeners skip setup; proxy / getState() return false for all keys
NuxtUse the Nuxt module
React SSRuseMediaQuery returns false; useResponsive returns the server-side snapshot
Hydration mismatchCall hydrate() with the server snapshot before first render

Without help the server renders every key as false. The ssrState option sets the values used instead whenever matchMedia (viewport) or ResizeObserver (container) is unavailable:

ts
const state = createResponsiveState(config, {
  ssrState: { desktop: true },
})

Keys that are not in the config are ignored, and keys that are not listed stay false. Once the browser takes over, the real values replace the seeded ones. The Nuxt module takes the same option. For hydration without a mismatch and for choosing the layout from the request, see SSR & Hydration.

ts
// Server: serialize the expected initial state
const initialState = { mobile: false, tablet: false, desktop: true }

// Client: hydrate before the first render
import { responsiveState } from 'responsive-media'
responsiveState.hydrate(initialState)

TypeScript types ​

SetConfigOptions ​

Second argument of setResponsiveConfig() / createResponsiveState() / createContainerState().

ts
interface SetConfigOptions {
  debounce?: number // ms delay for subscribe() listeners; on()/onEnter()/onLeave()/once()/waitFor() are never debounced. 0 disables (default)
  order?: string[] // explicit breakpoint order for isAbove()/isBelow()/between(); defaults to config key insertion order
  ssrState?: Record<string, boolean> // values used instead of `false` when matchMedia / ResizeObserver is unavailable (SSR)
}

BreakpointHelpers ​

Return type of useBreakpoints() (Vue and React — same shape, current is a ComputedRef<K | null> in Vue and a plain K | null in React). K is the key type: string by default, and the keys of your config when the hooks come from defineResponsive().

ts
interface BreakpointHelpers<K extends string = string> {
  current: K | null
  isAbove(key: K): boolean
  isBelow(key: K): boolean
  between(from: K, to: K): boolean
}