README.md

June 5, 2026 · View on GitHub

ngx-mq logo

Signal-powered breakpoints & media queries for Angular


CI status coverage npm version npm downloads minzipped size license

Documentation   ·   Live demo   ·   Why ngx-mq?


Overview

A responsive value is just a signal: read it in the template, compose it, and never wire up cleanup.

import { Component } from '@angular/core';
import { up } from 'ngx-mq';

@Component({
  selector: 'app-root',
  template: `
    @if (isDesktop()) {
      <app-sidebar />
    }
  `,
})
export class AppComponent {
  readonly isDesktop = up('lg');
}
  • Signal-native so it works anywhere signals do, zoneless apps included.
  • Zero boilerplate: no subscriptions, no unsubscribe, cleanup is automatic.
  • SSR-safe with a value you control on the server.
  • Batteries included: Tailwind, Bootstrap and Material presets, plus and / or / not.
  • Tiny: ~1.9 kB gzipped, and no RxJS.

Install

npm i ngx-mq        # Angular 20-22

Angular 19 -> ngx-mq@2  ·  Angular 16-18 -> ngx-mq@1

Then register your breakpoints once, at bootstrap:

import { provideBreakpoints } from 'ngx-mq';

bootstrapApplication(AppComponent, {
  providers: [provideBreakpoints({ sm: 640, md: 768, lg: 1024 })],
  // or a preset: provideTailwindBreakpoints() / provideBootstrapBreakpoints() / provideMaterialBreakpoints()
});

Call the helpers inside an injection context: a component field, a constructor, or a DI factory.

Examples

Show different layouts per screen size

readonly isMobile = down('md');
readonly isTablet = between('md', 'lg');
readonly isDesktop = up('lg');

Follow the system dark mode

readonly prefersDark = colorScheme('dark');

Drop hover styles on touch devices

// `hover()` has no direct inverse, so compose it
readonly isTouchLike = not(hover());

Combine any conditions

// Large screen, in landscape, with a hover-capable pointer
readonly isLandscapeDesktop = and(up('lg'), orientation('landscape'), hover());

// Small screens OR a reduced-motion preference
readonly prefersSimpleUi = or(down('md'), reducedMotion());

Respect reduced motion

readonly reduceMotion = reducedMotion();

Anything else, with a raw query

readonly isRetina = matchMediaSignal('(min-resolution: 2dppx)');

Why ngx-mq?

Angular's CDK ships BreakpointObserver, which works well but is built around RxJS and raw query strings. ngx-mq is built for the signals era: read a value in the template, subscribe to nothing, clean up automatically.

ngx-mqCDK BreakpointObserver
ReactivitySignal<boolean>Observable<BreakpointState>
CleanupAutomatic via DestroyRefManual (takeUntilDestroyed)
Named breakpointsTailwind / Bootstrap / Material or your ownMaterial breakpoints or raw strings
Media-feature helperscolorScheme, hover, pointer, ...Raw query strings
Compositionand / or / notRxJS operators
SSRConfigurable static valueHandle it yourself
Footprint~1.9 kB standalonePart of @angular/cdk

Documentation

Spin it up in seconds on StackBlitz, no setup required.

Full API reference, guides and recipes live at martsinlabs.github.io/ngx-mq.

API reference (quick view)

Every query helper returns a Signal<boolean> and accepts an optional options argument (CreateMediaQueryOptions).

Breakpoints

HelperArgumentstrue when
upbpviewport width >= bp
downbpviewport width < bp (exclusive)
betweenminBp, maxBpviewport width is in [minBp, maxBp)

down and between upper bounds are exclusive: a small epsilon (default 0.02, set via provideBreakpointEpsilon) is subtracted from the max so adjacent ranges never overlap.

Media features

HelperArgumentstrue when
orientation'portrait' | 'landscape'the screen orientation matches
colorScheme'light' | 'dark'the system color scheme matches
displayModeDisplayModeOptionthe display mode matches (PWA detection)
reducedMotionnonethe user prefers reduced motion
prefersContrast'more' | 'less' | 'no-preference' | 'custom'the user's contrast preference matches
hovernonethe primary pointer can hover
anyHovernoneany available pointer can hover
pointer'fine' | 'coarse' | 'none'the primary pointer matches
anyPointer'fine' | 'coarse' | 'none'any available pointer matches
colorGamut'srgb' | 'p3' | 'rec2020'the display covers the gamut

Composition

HelperArgumentstrue when
and...conditions: Signal<boolean>[]every condition is true (empty: true)
or...conditions: Signal<boolean>[]any condition is true (empty: false)
notcondition: Signal<boolean>the condition is false

Custom queries

HelperArgumentsDescription
matchMediaSignalquery: stringA signal for any raw CSS media query

Providers

ProviderArgumentDescription
provideBreakpointsbps: MqBreakpointsRegisters a custom breakpoint map
provideTailwindBreakpointsnoneRegisters the Tailwind preset
provideBootstrapBreakpointsnoneRegisters the Bootstrap preset
provideMaterialBreakpointsnoneRegisters the Material 2 preset
provideBreakpointEpsilonepsilon: numberSets the exclusive-bound epsilon (default 0.02)
provideSsrValuevalue: booleanSets the value signals report during SSR (default false)

Options and types

interface CreateMediaQueryOptions {
  ssrValue?: boolean; // value reported during SSR; overrides provideSsrValue
  debugName?: string; // shown for the signal in Angular DevTools
}

type MqBreakpoints = Record<string, number>;

type DisplayModeOption =
  | 'browser' | 'fullscreen' | 'standalone'
  | 'minimal-ui' | 'window-controls-overlay' | 'picture-in-picture';

Server-side rendering

matchMedia does not exist on the server, so each signal returns a static value during SSR and switches to the live result after hydration. Set the default with provideSsrValue(true), or override per call with up('lg', { ssrValue: true }).

Contributing

Contributions are welcome. See CONTRIBUTING.md and ARCHITECTURE.md.

License

MIT © Martsin Labs

Sponsors

Sentry

Sentry
Error tracking and performance monitoring.