Creating A Custom Svelte Media Query Store

In this short read we create a custom Svelte store to listen for Media Query changes. Super useful for when we want to react to the user changing the system theme or resizing the browser window.

If you just want to get your hands on the ready made custom store you can jump to the code, else let’s explore how to create this custom store together.

Svelte Custom Stores

Svelte exports a set of default stores, namely readable, writable, and derived.

We’re going to use the writable store and wrap our custom Media Query Listener store around it.

This is what we’re going for.

// Import our custom store
import mediaQueryStore from './mediaQueryStore.js';

// Create a dark mode preference store
const prefersDarkMode = mediaQueryStore('(prefers-color-scheme:dark)');

// This will log `true` when in dark mode, it'll automatically be
// called again when the user changes the system preference
$: console.log($prefersDarkMode);

The $ symbol is Svelte magic.

It means Svelte will automatically subscribe and unsubscribe to the store.

You could rewrite the code snippet above without magic like this.

// Import our custom store
import mediaQueryStore from './mediaQueryStore.js';

// Need to clean up the store when done
import { onDestroy } from 'svelte';

// Create a dark mode preference store
const prefersDarkMode = mediaQueryStore('(prefers-color-scheme:dark)');

// This will log `true` when in dark mode
const unsub = prefersDarkMode.subscribe((value) => {
    console.log(value);
});

// Unsubscribe from store when destroying this component
onDestroy(() => {
    unsub();
});

We’re going to need the writable Svelte store and our store factory only accepts one parameter, the Media Query it should observe, so this is the base for our custom store.

// We need to use the writable Svelte store
import { writable } from 'svelte/store';

/**
 * Svelte Media Query Store
 * @param mediaQueryString { string }
 */
export default (mediaQueryString) => {
    /* Store here */
};

Let’s see how we can listen for Media Query changes first.

Listening For Media Query Changes

We can use the MediaQueryList to listen for Media Query changes.

To start watching we use the matchMedia method.

// Create the MediaQueryList
const mql = window.matchMedia('(prefers-color-scheme:dark)');

// Listen for changes
mql.addEventListener('change', (e) => {
    // e.matches is true when user prefers dark mode
    console.log('Prefers dark mode', e.matches);
});

// mql.matches is true when user prefers dark mode
console.log('Prefers dark mode', mql.matches);

Fairly straightforward.

Let’s now move this logic into a Svelte store so it’s even easier to use.

Using MediaQueryList In A Custom Svelte Store

Let’s create a writable store inside our media query store and set up its default logic.

import { writable } from 'svelte/store';

/**
 * Svelte Media Query Store
 * @param mediaQueryString { string }
 */
export default (mediaQueryString) => {
    // We'll need the subscribe and set methods of the writable store
    const { subscribe, set } = writable(
        // The initial value of the store is undefined
        undefined,

        // This function is called on first subscribe call
        () => {
            // Construct
            // ...

            // This function is called on last unsubscribe call
            return () => {
                // Destroy
                // ...
            };
        }
    );

    // We only expose the subscribe method to the outside world
    return { subscribe };
};

Now let’s add our MediaQueryList logic to the store.

import { writable } from 'svelte/store';

/**
 * Svelte Media Query Store
 * @param mediaQueryString { string }
 */
export default (mediaQueryString) => {
    const { subscribe, set } = writable(undefined, () => {
        // Start observing media query
        let mql = window.matchMedia(mediaQueryString);

        // Set first media query result to the store
        set(mql.matches);

        // Called when media query state changes
        const onchange = () => set(mql.matches);

        // Listen for changes (need to support old `addListener` interface)
        'addEventListener' in mql
            ? mql.addEventListener('change', onchange)
            : mql.addListener(onchange);

        // when no more listeners
        return () => {
            // stop listening (need to support old `removeListener` interface)
            'removeEventListener' in mql
                ? mql.removeEventListener('change', onchange)
                : mql.removeListener(onchange);
        };
    });

    return { subscribe };
};

That’s it, we can now use this store in our Svelte projects like shown below.

import mediaQueryStore from './mediaQueryStore.js';

const prefersDarkMode = mediaQueryStore('(prefers-color-scheme:dark)');

$: console.log($prefersDarkMode);

I share web dev tips on Twitter, if you found this interesting and want to learn more, follow me there

Or join my newsletter

More articles More articles