Skip to content

Utilities & Presets

Utilities

syncCSSVars(options?) → stop

Syncs all breakpoint keys to CSS custom properties (1 / 0) on document.documentElement (or a custom element). Automatically removes properties for keys removed during a config change.

ts
const stop = state.syncCSSVars({ element: document.body, prefix: '--bp-' })
// → --bp-mobile: 1; --bp-desktop: 0; …
stop() // cleanup
OptionDefaultDescription
elementdocument.documentElementTarget HTML element
prefix'--responsive-'CSS custom property name prefix

emitDOMEvents(target?, options?) → stop

Dispatches DOM CustomEvents on target whenever breakpoints change:

  • responsive:change — fires on any state change; event.detail is the full state snapshot
  • responsive:mobile:enter — fires when mobile becomes true
  • responsive:mobile:leave — fires when mobile becomes false
ts
const stop = state.emitDOMEvents(document, { prefix: 'bp:' })

document.addEventListener('bp:change', (e) => console.log(e.detail))
document.addEventListener('bp:mobile:enter', () => initDrawer())
document.addEventListener('bp:desktop:leave', () => destroyDesktopChart())

stop()
OptionDefaultDescription
prefix'responsive:'Custom event name prefix

toSignal(key, factory) → Signal

Binds a breakpoint key to a writable signal from any signals library. The signal is kept in sync via on().

ts
// @preact/signals-core
import { signal } from '@preact/signals-core'
const isMobile = state.toSignal('mobile', signal)
isMobile.value // reactive boolean

// Angular signal
import { signal } from '@angular/core'
const isMobile = state.toSignal('mobile', signal)

// Vue ref
import { ref } from 'vue'
const isMobile = state.toSignal('mobile', ref)

getMediaQueries() → Record<string, string>

Returns the generated CSS media query strings for each breakpoint key.

ts
const mq = state.getMediaQueries()
// { mobile: '(max-width: 600px)', desktop: '(min-width: 961px)' }

getState<T>() → T

Returns a stable snapshot of the current state. Same reference between changes — safe for React's useSyncExternalStore.

getOrder() → string[]

Returns the configured breakpoint order array (or empty array if not set).

hydrate(initialState): SSR hydration

Sets initial state from a server-side snapshot to prevent layout shift. Only updates keys that exist in the current config.

ts
state.hydrate({ mobile: false, tablet: false, desktop: true })

destroy()

Removes all matchMedia / ResizeObserver listeners, clears all subscribers, and cancels any pending debounce timer.

toMediaQueryString(conditions): standalone utility

Converts a MediaQueryConfig to a CSS media query string. Useful for CSS-in-JS or debugging.

ts
import { toMediaQueryString } from 'responsive-media'

toMediaQueryString([
  { type: 'min-width', value: 768 },
  { type: 'max-width', value: 1024 },
])
// → "(min-width: 768px) and (max-width: 1024px)"

toMediaQueryString([
  [{ type: 'max-width', value: 600 }],
  [{ type: 'orientation', value: 'portrait' }],
])
// → "(max-width: 600px), (orientation: portrait)"

match(state, map, fallback?): standalone utility

Returns the first value in map whose key is true in state. Priority follows map insertion order.

ts
import { match } from 'responsive-media'
import { responsiveState } from 'responsive-media'

const cols = match(responsiveState.proxy, { mobile: 1, tablet: 2, desktop: 4 })
const View = match(responsiveState.proxy, { mobile: MobileMenu, desktop: DesktopNav })
const label = match(responsiveState.proxy, { sm: 'Compact', lg: 'Full' }, 'Default')

subscribeMediaQuery(query, callback): standalone utility

Low-level reactive wrapper around a single raw CSS media query string. Framework-agnostic — the Vue and React adapters use this internally.

ts
import { subscribeMediaQuery } from 'responsive-media'

const off = subscribeMediaQuery('(prefers-color-scheme: dark)', (matches) => {
  document.body.classList.toggle('dark', matches)
})
off() // cleanup

Presets

Import from responsive-media/presets or from the main entry point.

ResponsiveConfig (default)

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

TailwindPreset + TailwindOrder

Mutually exclusive Tailwind CSS v3/v4 breakpoints:

KeyRange
xs≤ 639px
sm640 – 767px
md768 – 1023px
lg1024 – 1279px
xl1280 – 1535px
2xl≥ 1536px
ts
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media'

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

BootstrapPreset + BootstrapOrder

Mutually exclusive Bootstrap 5 breakpoints:

KeyRange
xs≤ 575px
sm576 – 767px
md768 – 991px
lg992 – 1199px
xl1200 – 1399px
xxl≥ 1400px
ts
import { createResponsiveState, BootstrapPreset, BootstrapOrder } from 'responsive-media'

const state = createResponsiveState(BootstrapPreset, { order: [...BootstrapOrder] })

AccessibilityPreset

User-preference media queries. Multiple keys can be true simultaneously.

KeyMatches when …
darkprefers-color-scheme: dark
lightprefers-color-scheme: light
reducedMotionprefers-reduced-motion: reduce
highContrastprefers-contrast: more
lowContrastprefers-contrast: less
noHoverhover: none (touch / stylus devices)
coarsePointerpointer: coarse (finger-sized input)
forcedColorsforced-colors: active (Windows HCM)
printprint media type
ts
import { createResponsiveState, AccessibilityPreset } from 'responsive-media'

const a11y = createResponsiveState(AccessibilityPreset)

a11y.onEnter('dark', () => applyDarkTheme())
a11y.onEnter('reducedMotion', () => disableAnimations())
a11y.onEnter('print', () => hideNonPrintable())