Skip to content

Vue Integration ​

Vue composables are included in the main bundle and are activated when Vue is installed as a peer dependency.

Plugin registration ​

ts
import { createApp } from 'vue'
import { ResponsivePlugin } from 'responsive-media/vue'

const app = createApp(App)

app.use(ResponsivePlugin, {
  sm: [{ type: 'max-width', value: 767 }],
  lg: [{ type: 'min-width', value: 1024 }],
})

app.mount('#app')

Typed hooks from your config ​

defineResponsive(config, options?)

Applies config to the shared state and returns useResponsive, useBreakpoints, useResponsiveValue and a plugin, 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/vue'

export const { useResponsive, useBreakpoints, plugin } = 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() helpers accept only those keys: isAbove('nope') is a compile error. The generic parameter, useBreakpoints<K>(), is optional on the plain composable too.
  • plugin — app.use(plugin) provides the state through RESPONSIVE_KEY, the same way ResponsivePlugin does.

The plugin accepts install options: app.use(plugin, { ssrState, hydration, hydrating }). ssrState is the snapshot the server rendered, which the client hydrates with; hydration is 'immediate' or 'deferred'; hydrating says explicitly whether the app is hydrating server markup; commit ('auto' or 'manual') and commitTimeout decide when the real state arrives, and commitHydration(), returned next to plugin, applies it in 'manual' mode. See SSR & Hydration. On the server the plugin gives every app its own state, so concurrent requests never share one.

The call replaces the configuration an earlier ResponsivePlugin or setResponsiveConfig() set, because they all share one state. In Nuxt the module makes this call for you.

Responsive State ​

useResponsive<T>()

Returns the Vue reactive responsive state. Reactive in templates and computed properties. Accepts a generic type parameter to narrow the returned state — see ConfigToState<T>.

vue
<script setup lang="ts">
import { useResponsive } from 'responsive-media/vue'

type MyState = { sm: boolean; lg: boolean }
const state = useResponsive<MyState>()
</script>

<template>
  <MobileNav v-if="state.sm" />
  <DesktopNav v-else />
</template>

Breakpoint Helpers ​

useBreakpoints()

Returns reactive ordered breakpoint helpers. All methods react to viewport changes in templates. Like useResponsive() above, it checks inject(RESPONSIVE_KEY, null) first and falls back to the global singleton mirror if nothing was provided — so a custom state provided via provide(RESPONSIVE_KEY, customState) is honored consistently by both composables.

vue
<script setup>
import { useBreakpoints } from 'responsive-media/vue'

const { current, isAbove, isBelow, between } = useBreakpoints()
</script>

<template>
  <span>Current: {{ current }}</span>
  <DesktopNav v-if="isAbove('sm')" />
  <MobileNav v-else />
  <TabletOnly v-if="between('sm', 'lg')" />
</template>

current is a ComputedRef<string | null>. isAbove, isBelow, between are plain functions — reactive because they read from the Vue reactive state.

Providing a custom state ​

RESPONSIVE_KEY

The injection key useResponsive() and useBreakpoints() both read from. Provide it manually (bypassing ResponsivePlugin) to point them at a state you created yourself with createResponsiveState() — useful for an isolated responsive context, e.g. inside one subtree of a larger app.

vue
<script setup>
import { reactive } from 'vue'
import { provide } from 'vue'
import { RESPONSIVE_KEY } from 'responsive-media/vue'
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media'

const customState = reactive(
  createResponsiveState(TailwindPreset, { order: [...TailwindOrder] }).getState(),
)
provide(RESPONSIVE_KEY, customState)
</script>

Responsive Values ​

useResponsiveValue(map, fallback?)

ComputedRef<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.

vue
<script setup>
import { useResponsiveValue } from 'responsive-media/vue'

const columns = useResponsiveValue({ mobile: 1, tablet: 2, desktop: 4 })
const title = useResponsiveValue({ mobile: 'Menu' }, 'Navigation')
</script>

<template>
  <Grid :columns="columns" />
</template>

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 }

Reactive booleans for 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.

vue
<script setup>
import { useUserPreferences } from 'responsive-media/vue'

const prefs = useUserPreferences()
</script>

<template>
  <Parallax v-if="!prefs.reducedMotion" />
</template>

Viewport Size ​

useViewportSize(options?)

{ width: number; height: number; stop: () => void }

The window size in pixels, reactive, for the moments you need the number and not a breakpoint. It is 0 by 0 on the server.

vue
<script setup>
import { useViewportSize } from 'responsive-media/vue'

const size = useViewportSize({ throttle: 200 })
</script>

<template>
  <span>{{ size.width }} × {{ size.height }}</span>
</template>
  • throttle — number · default: 100. Milliseconds between updates while the window is resized; 0 updates on every event.

Inside a component it stops on unmount; call stop() yourself anywhere else.

Media Query ​

useMediaQuery(query)

Ref<boolean> & { stop: () => void }

Returns a reactive ref for a raw CSS media query string. Inside a component, cleans up automatically on onUnmounted. The returned ref also always carries a .stop() method, so code that calls useMediaQuery() outside an active component instance — a plain factory function, a Pinia store's setup(), etc. — can clean up the underlying matchMedia listener manually, since there's no component lifecycle to do it automatically.

vue
<script setup>
import { useMediaQuery } from 'responsive-media/vue'

const isDark = useMediaQuery('(prefers-color-scheme: dark)')
const canHover = useMediaQuery('(hover: hover)')
</script>

<template>
  <DarkTheme v-if="isDark" />
</template>
ts
// Outside a component — e.g. a plain module-level helper or store setup
import { useMediaQuery } from 'responsive-media/vue'

const isDark = useMediaQuery('(prefers-color-scheme: dark)')
// ... later, when you're done with it:
isDark.stop()

A reactive query ​

query can be a string, a ref or a getter. When it changes, the composable drops the old listener and subscribes to the new query.

vue
<script setup>
import { ref } from 'vue'
import { useMediaQuery } from 'responsive-media/vue'

const breakpoint = ref(700)
const wide = useMediaQuery(() => `(min-width: ${breakpoint.value}px)`)
</script>

Container State ​

useContainerState(elementRef, config, options?)

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

vue
<script setup>
import { useTemplateRef } from 'vue'
import { useContainerState } from 'responsive-media/vue'

const cardRef = useTemplateRef < HTMLDivElement > 'card'
const cardState = useContainerState(cardRef, {
  compact: [{ type: 'max-width', value: 300 }],
  wide: [{ type: 'min-width', value: 600 }],
})
</script>

<template>
  <div ref="card">
    <CompactLayout v-if="cardState.compact" />
    <WideLayout v-else-if="cardState.wide" />
    <DefaultLayout v-else />
  </div>
</template>