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);