ngx-mq
    Preparing search index...

    ngx-mq

    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?


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

    readonly isMobile = down('md');
    readonly isTablet = between('md', 'lg');
    readonly isDesktop = up('lg');
    readonly prefersDark = colorScheme('dark');
    
    // `hover()` has no direct inverse, so compose it
    readonly isTouchLike = not(hover());
    // 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());
    readonly reduceMotion = reducedMotion();
    
    readonly isRetina = matchMediaSignal('(min-resolution: 2dppx)');
    

    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-mq CDK BreakpointObserver
    Reactivity Signal<boolean> Observable<BreakpointState>
    Cleanup Automatic via DestroyRef Manual (takeUntilDestroyed)
    Named breakpoints Tailwind / Bootstrap / Material or your own Material breakpoints or raw strings
    Media-feature helpers colorScheme, hover, pointer, ... Raw query strings
    Composition and / or / not RxJS operators
    SSR Configurable static value Handle it yourself
    Footprint ~1.9 kB standalone Part of @angular/cdk

    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

    Helper Arguments true when
    up bp viewport width >= bp
    down bp viewport width < bp (exclusive)
    between minBp, maxBp viewport 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

    Helper Arguments true when
    orientation 'portrait' | 'landscape' the screen orientation matches
    colorScheme 'light' | 'dark' the system color scheme matches
    displayMode DisplayModeOption the display mode matches (PWA detection)
    reducedMotion none the user prefers reduced motion
    prefersContrast 'more' | 'less' | 'no-preference' | 'custom' the user's contrast preference matches
    hover none the primary pointer can hover
    anyHover none any 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

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

    Custom queries

    Helper Arguments Description
    matchMediaSignal query: string A signal for any raw CSS media query

    Providers

    Provider Argument Description
    provideBreakpoints bps: MqBreakpoints Registers a custom breakpoint map
    provideTailwindBreakpoints none Registers the Tailwind preset
    provideBootstrapBreakpoints none Registers the Bootstrap preset
    provideMaterialBreakpoints none Registers the Material 2 preset
    provideBreakpointEpsilon epsilon: number Sets the exclusive-bound epsilon (default 0.02)
    provideSsrValue value: boolean Sets 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';

    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 }).

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

    MIT © Martsin Labs

    Sentry

    Sentry
    Error tracking and performance monitoring.