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)
// (min-width: 768px) and (max-width: 1023px)
;[
{ type: 'min-width', value: 768 },
{ type: 'max-width', value: 1023 },
]OR (nested array)
// (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:
// 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
type | Example value | Generated query |
|---|---|---|
min-width | 768 | (min-width: 768px) |
max-width | 1023 | (max-width: 1023px) |
min-height | 600 | (min-height: 600px) |
max-height | 900 | (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:
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).
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)
| Key | Range |
|---|---|
mobile | ≤ 600px |
tablet | 601 – 960px |
desktop | ≥ 961px |
Creating Independent Instances
createResponsiveState()
Creates independent instances — useful for per-request SSR, multiple independent contexts, or testing:
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).
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.
| Scenario | Behaviour |
|---|---|
typeof window === 'undefined' | All listeners skip setup; proxy / getState() return false for all keys |
| Nuxt | Use the Nuxt module |
| React SSR | useMediaQuery returns false; useResponsive returns the server-side snapshot |
| Hydration mismatch | Call 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:
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.
// 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().
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().
interface BreakpointHelpers<K extends string = string> {
current: K | null
isAbove(key: K): boolean
isBelow(key: K): boolean
between(from: K, to: K): boolean
}