Skip to content
Carlo Fontanos
Carlo Fontanos

Carlo Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

Responsive JavaScript Without Resize Listeners

C
Carlo Fontanos
· 2 min read

Here's a pattern that shows up in almost every aging frontend codebase:

window.addEventListener('resize', () => {
    if (window.innerWidth < 768) enableMobileMenu();
    else disableMobileMenu();
});

Three problems: it runs dozens of times per second during a drag-resize, the 768 inevitably drifts apart from the CSS breakpoint, and it doesn't run on load without an extra manual call. matchMedia fixes all three.

const mobile = window.matchMedia('(max-width: 767px)');

function apply(mq) {
    mq.matches ? enableMobileMenu() : disableMobileMenu();
}

apply(mobile);                              // initial state
mobile.addEventListener('change', apply);   // fires ONLY when crossing 767px

The change event fires exactly once per boundary crossing - not on every pixel of resize. The browser does the watching; your code only runs when the answer changes.

It speaks full media query, not just width

Anything CSS can query, JS can now react to:

// Dark mode toggled at the OS level, live:
matchMedia('(prefers-color-scheme: dark)')
    .addEventListener('change', e => setTheme(e.matches ? 'dark' : 'light'));

// Respect reduced motion in JS-driven animations:
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
scroller.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });

// Pointer type - show hover hints only where hover exists:
if (matchMedia('(hover: hover)').matches) enableTooltips();

That reduced-motion line belongs in every scroll-behavior and animation call you write - the CSS side of that story is its own topic.

Keeping JS and CSS breakpoints in one place

The drift problem disappears if CSS owns the value and JS reads it:

:root { --bp-mobile: 767px; }
const bp = getComputedStyle(document.documentElement)
    .getPropertyValue('--bp-mobile').trim();
const mobile = matchMedia(`(max-width: ${bp})`);

One source of truth, and redesigns stop breaking your JS silently.

When you still need resize

matchMedia answers "which side of a breakpoint am I on". For "how big is this element exactly" (canvas sizing, virtualized lists), the right tool is ResizeObserver on the element itself - window resize listeners are almost never the right answer anymore.

Support: matchMedia itself is ancient (IE10+); the modern addEventListener syntax on it works in everything current, with .addListener as the legacy spelling for very old Safari.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Your details are only used to reply to you.

Keep reading