A tiny, performant, and intuitive Vue 3 plugin for working with responsive breakpoints and media queries at runtime.
Use it when you want Vue components to react to breakpoints, orientation, hover support, color scheme, reduced motion, and contrast preferences without manually wiring window.matchMedia.
- Vue-friendly API: use
$matchesin templates oruseMatches()in<script setup>. - Breakpoint presets included: start with Bootstrap 5 by default, or switch to Tailwind CSS, Vuetify, Bulma, and more.
- Custom breakpoints: define your own app-specific intervals with a plain object.
- Media query support: react to viewport width,
orientation,hover,prefers-color-scheme,prefers-contrast, andprefers-reduced-motion. - SSR safe: it does not touch
windowduring server rendering. - Zero runtime dependencies: lightweight and built on the browser's native
window.matchMedia. - Automatic cleanup: listeners are attached to
MediaQueryListobjects and cleaned up with the Vue app.
Use the package manager your project already uses.
| Package manager | Command |
|---|---|
| pnpm | pnpm add vue-responsiveness |
| yarn | yarn add vue-responsiveness |
| npm | npm install vue-responsiveness |
In main.ts:
import { createApp } from 'vue'
import App from './App.vue'
import { VueResponsiveness } from 'vue-responsiveness'
createApp(App)
.use(VueResponsiveness)
.mount('#app')That is enough to use the default Bootstrap 5 breakpoints. For other frameworks (Tailwind CSS, Vuetify, etc.), or your own intervals, see Breakpoint management.
You can use either option below, or mix them in the same app. Use $matches for simple template conditions and useMatches() when you need responsive state in <script setup>.
Every component can read $matches in its template, without importing.
<template>
<div>
<p>Current breakpoint: {{ $matches.current || 'unknown' }}</p>
<p v-if="$matches.md.min">
This is visible on medium screens and larger.
</p>
<p v-if="$matches.sm.max">
This is visible on small screens and smaller.
</p>
<p v-if="$matches.prefers.colorScheme === 'dark'">
Dark mode is preferred.
</p>
</div>
</template>Use the composable when you need responsive state in computed values, watchers, or methods.
<script setup lang="ts">
import { computed } from 'vue'
import { useMatches } from 'vue-responsiveness'
const matches = useMatches()
const showSidebar = computed(() => matches.md.min)
</script>
<template>
<main>
<aside v-if="showSidebar">
Sidebar navigation
</aside>
<nav v-else>
Mobile navigation
</nav>
</main>
</template>You can also use helper methods:
const showDesktopNav = computed(() => matches.isMin('lg'))
const showMobileNav = computed(() => matches.isMax('sm'))
const isTabletLayout = computed(() => matches.isOnly('md'))The plugin uses Presets.Bootstrap_5 by default. To use a different preset, pass it as the second argument to app.use().
import { createApp } from 'vue'
import App from './App.vue'
import { VueResponsiveness, Presets } from 'vue-responsiveness'
createApp(App)
.use(VueResponsiveness, Presets.Tailwind_CSS)
.mount('#app')Available presets:
Ant_Design, Bootstrap_3, Bootstrap_4, Bootstrap_5, Bulma, Chakra, Foundation, Ionic, Master_CSS, Material_Design, Materialize, Material_UI, Quasar, Semantic_UI, Skeleton, Tailwind_CSS, Vuetify, Windi_CSS
Bootstrap 5 preset details
Presets.Bootstrap_5 = {
xs: 0,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
xxl: 1400,
}Vue Responsiveness is designed to have one app-wide breakpoint configuration. Register the plugin once, close to createApp(), so all components read from the same $matches object.
Pass an object whose keys are your breakpoint names and whose values are the minimum width in pixels. The plugin calculates each breakpoint's maximum width from the next breakpoint.
app.use(VueResponsiveness, {
small: 0,
medium: 777,
large: 1234
})Then use those names in templates:
<!-- @media (min-width: 777px) and (max-width: 1233.9px) -->
<template v-if="$matches.medium.only">
<TwoColumnLayout />
</template>$matches and useMatches() expose the same reactive object. Use $matches in templates and useMatches() in JavaScript or TypeScript.
Each breakpoint key, such as sm, md, lg, or one of your custom names, contains three booleans.
| Name | Type | Description | Example Access |
|---|---|---|---|
current |
string |
The breakpoint interval that currently matches. | $matches.current |
[breakpointKey].min |
boolean |
true at this breakpoint width and above. |
$matches.md.min |
[breakpointKey].max |
boolean |
true up to and including this breakpoint range. |
$matches.lg.max |
[breakpointKey].only |
boolean |
true only inside this breakpoint range. |
$matches.sm.only |
These are handy in computed values and methods.
| Name | Type | Description | Example usage |
|---|---|---|---|
isMin(breakpoint) |
(breakpointKey) => boolean |
Matches at this breakpoint and above. | matches.isMin('md') |
isMax(breakpoint) |
(breakpointKey) => boolean |
Matches inside this breakpoint range and below. | matches.isMax('sm') |
isOnly(breakpoint) |
(breakpointKey) => boolean |
Matches only inside this breakpoint range. | matches.isOnly('lg') |
These values mirror common CSS media queries, but are available directly in Vue.
| Name | Type | Description | Media query equivalent |
|---|---|---|---|
orientation |
'portrait' | 'landscape' |
The current device orientation | @media (orientation: *) |
hover |
'none' | 'hover' |
The hover capability of the device | @media (hover: *) |
prefers.reducedMotion |
'no-preference' | 'reduce' |
User preference for reduced motion | @media (prefers-reduced-motion: *) |
prefers.colorScheme |
'light' | 'dark' |
User preference for color scheme | @media (prefers-color-scheme: *) |
prefers.contrast |
'no-preference' | 'more' | 'less' | 'custom' |
User preference for contrast | @media (prefers-contrast: *) |
When testing a component that uses $matches, install the plugin through Vue Test Utils global.plugins.
import { shallowMount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
import { VueResponsiveness } from 'vue-responsiveness'
describe('<MyComponent />', () => {
it('should render', () => {
const wrapper = shallowMount(MyComponent, {
global: {
plugins: [VueResponsiveness]
}
})
expect(wrapper.exists()).toBe(true)
})
})- Vue Responsiveness uses
window.matchMedia(queryString), the same browser API behind CSS media queries. - It listens to
changeevents onMediaQueryListobjects, so updates happen only when a query starts or stops matching. - It avoids global
resizelisteners and keeps one responsive state object per Vue app. - It is SSR safe because browser-only APIs are read after the plugin is installed in the client.
Found something confusing, broken, or missing? Open an issue.