breakpoint-helper

Small helper library to work with layout breakpoints1 in Javascript.

Demo - Resize me!

Demo

Resize the window above by dragging the bottom right corner. The current breakpoint name is displayed in the middle of the window.

Core functionality

Introduction

In CSS it is common practice to give layout breakpoints, used in width-based media queries, names, such as 'mobile', 'tablet', 'desktop' or 'sm', 'md', 'lg', to be able to easily reference them instead of having to remember exact values.

Often times the the CSS breakpoints apply styling changes that need to be mirrored in Javascript, e.g. display cards in a slider on small screens (with Javascript) and as a grid on larger screens (without Javascript).

breakpoint-helper is a thin wrapper around window.matchMedia that aims to make working with layout breakpoints in Javascript more convenient by allowing to reference the breakpoints by name instead of by value ('sm' vs. 765px) and providing a convenient API to set and remove event listeners on media queries.

Installation

Install via npm or yarn:

npm install --save breakpoint-helper
# or
yarn add breakpoint-helper

Usage

The breakpoint-helper exports a factory function to create a breakpoint-helper instance. The factory function expects to receive the breakpoints it should work with. There are different ways to provide the breakpoints, the best choice depends on the specific project setup.

NOTE: All initialization options expect the breakpoints to be ordered from small to large.

Initialize with Javascript object

The breakpoints can defined in an object where the object keys represent the breakpoint names and the values the screen widths. The values should be of type string and include a CSS unit, both px and em are supported.

import breakpointHelper from 'breakpoint-helper';

const bph = breakpointHelper({
  xs: '416px',
  sm: '600px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
  xxl: '1520px',
});

export default bph;

Initialize using CSS custom properties

Declare custom properties on the :root selector using the prefix --bph-:

:root {
  --bph-xs: 416px;
  --bph-sm: 600px;
  --bph-md: 768px;
  --bph-lg: 1024px;
  --bph-xl: 1280px;
  --bph-xxl: 1520px;
}

Then initialize breakpoint-helper passing the string 'custom' as an argument:

// src/utils/bph.js
import breakpointHelper from 'breakpoint-helper';

const bph = breakpointHelper('custom');

export default bph;

Initialize with Sass map (share CSS breakpoints with Javascript)

Breakpoint-helper provides a sass mixin that allows the use of a Sass map to define the breakpoints. To use this method use the mixin in your Sass code by passing it the breakpoints as argument:

// Import the mixin, path may vary depending on implementation
@import './node_modules/breakpoint-helper/src/breakpoint-helper';

// Define a map of breakpoints
$bps: (
  'xs': 416px,
  'sm': 600px,
  'md': 768px,
  'lg': 1024px,
  'xl': 1280px,
  'xxl': 1520px,
);

// Use the mixin
@include breakpoint-helper($bps);

Then initialize breakpoint-helper with the string 'meta' as argument:

// src/utils/bph.js
import breakpointHelper from 'breakpoint-helper';

const bph = breakpointHelper('meta');

export default bph;

What is happening here?

The Sass mixin will create a ruleset for the class .breakpoint-helper with a single font-family declaration, the font-family value will be a serialized string of the breakpoint map:

.breakpoint-helper {
  font-family: 'xs=416px&sm=600px&md=768px&lg=1024px&xl=1280px&xxl=1520px';
}

When breakpoint-helper gets initialized it will create a <meta> element in the document's <head> tag with the class breakpoint-helper, read the font-famliy CSS value and deserialize it.

NOTE: This method does not require the use of Sass or the mixin per se. All that is required is the class .breakpoint-helper with the serialized breakpoints as font-family value.

Typescript

As of v2.0.0 The library is written in Typescript and types definitions are available.

Usage with React

To use breakpoint-helper in React, setup an instance using any of the methods above and use it within a useEffect hook. You can then use a useState hook to use it inside your component's render function.

import { useEffect, useState } from 'react';
import { listen } from './src/utils/bph';

const MyComponent = (props) => {
  const [isMatching, setIsMatching] = useState(true); // Set a default value in case you are using SSR

  useEffect(() => {
    const listener = listen('sm', ({ matches }) => {
      setIsMatching(matches);
    });
    return () => listener.off(); // Remove the event listener on component unmount
  }, []);

  return isMatching ? (
    <div className="is-matching">Matching the "sm" breakpoint.</div>
  ) : (
    <div className="not-matching">Not matching the "sm" breakpoint.</div>
  );
};

Methods

Each breakpoint-helper instance returns methods to work with the breakpoints.

In larger projects it is convenient to create a reusable breakpoint-helper instance module and export the returned methods for easier usage.

import breakpointHelper from 'breakpoint-helper';

// Could be any other of the initialization methods
const instance = breakpointHelper({
  xs: '416px',
  sm: '600px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
  xxl: '1520px',
});

export const {
  getBreakpoints,
  getMediaQuery,
  isMatching,
  listen,
  listenAll,
} = instance; // Destructure methods and export for conveninece

export default instance;

NOTE: The following code examples assume the use of the instance above.

getBreakpoints()

Get all breakpoints as an object. Useful for debugging or passing breakpoint values to other libraries.

Returns

Example

import { getBreakpoints } from './src/utils/bph';

const breakpoints = getBreakpoints();

console.log(breakpoints);
// {
//   xs: '416px',
//   sm: '600px',
//   md: '768px',
//   lg: '1024px',
//   xl: '1280px',
//   xxl: '1520px',
// }

getMediaQuery(name, [useMax=false])

Get a min-width, max-width or min-width and max-width media query by breakpoint name.

Arguments

Returns

Example

import { getMediaquery } from './src/utils/bph';

const mq = getMediaquery('md');
console.log(mq);
// '(min-width: 768px)'

const mqMax = getMediaquery('md', true);
console.log(mqMax);
// '(max-width: 767px)'

const mqMinMax = getMediaquery(['md', 'lg']);
console.log(mqMax);
// '(min-width: 768px) and (max-width: 1023px)'

isMatching(name, [useMax=false])

Check if a breakpoint or breakpoint range is currently matching.

Arguments

Returns

Example

import { isMatching } from './src/utils/bph';

if (isMatching('md')) {
  // Do something
} else {
  // Do something else
}

if (isMatching(['md', 'lg'])) {
  // Screen width is between 'md' and 'lg'
}

listen(options, callback)

Listen to a breakpoint or breakpoint range change and execute a callback function. The callback function will receive a MediaQueryList object as parameter that can be used to check wether the breakpoint media query is matching or not. The callback function is called once on listener creation, it is possible to opt out of this behavior via options.

Arguments

Returns

Example

import { listen } from './src/utils/bph';

// Destructure the `MediaQueryList.matches` property
const callback = ({ matches }) => {
  if (matches) {
    // Do somthing
  } else {
    // Do somthing else
  }
};

const listener = listen('md', callback);

// Remove the event listener
listener.off();

// Activate it again
listener.on();

Using an options object instead of a breakpoint name:

import { listen } from './src/utils/bph';

const listener = listen(
  {
    name: 'md',
    useMax: true,
    immediate: false,
  },
  callback
);

const callback = ({ matches }) => {
  // ...
};

listenAll(callback, [options])

Listen to all breakpoints matching or un-matching and execute a callback function. The callback function will receive an array of the matching breakpoint names in reverse order as a parameter. That means the largest breakpoint name (or smallest when using options.useMax) comes first in the array. The array will be empty if no breakpoints are matching.

Arguments

Returns

Example

import { listenAll } from './src/utils/bph';

const listener = listenAll(callback);

const callback = (bps) => {
  // Get the first breakpoint in the `bps` array.
  const match = bps[0];

  // If the largest matching breakpoint is 'lg', it will
  // be the first in the array `['lg', 'md', 'sm', 'xs',]`.

  switch (match) {
    case 'lg':
      // Do something if the breakpoint is 'lg'
      break;
    case 'md':
      // Do something if the breakpoint is 'md'
      break;
    case 'sm':
      // Do something if the breakpoint is 'sm'
      break;

    default:
      // Do something if another breakpoint is matching or none is
      break;
  }
};

// Remove the event listener
listener.off();

// Activate it again
listener.on();

Limit the breakpoints by passing using options.listenTo:

import { listenAll } from './src/utils/bph';

const listener = listenAll(callback, {
  // Only listen to the breakpoints 'xl', 'lg' and 'sm'.
  listenTo: ['xl', 'lg', 'sm'],
  // Use `max-width` media queries instead of `min-width`
  useMax: true,
});

const callback = (bps) => {
  // ...
};

Size & compatibility

Depending on the bundle the library is about 1.1 kB when gziped.

The library is compatible with modern browsers and should also work with IE11 (not tested).

Roadmap

Motivation

I kept needing something like this and was copying the same script from project to project, so I decided to open-source it. On one hand it might help somebody that has the same need as I do and on the other it allows me to install just as another dependency.

Credits

The initialization via metatag and the serialized font-family value is taken from Zurb's Foundation.

Notes

1) Browser window widths at which styles/functionality changes to adapt for wider/narrower screens.

2) The useMax argument will be ignored when name is an array.

3) When using useMax breakpoint-helper will subtract 1px from the breakpoint value to prevent overlap. If the breakpoint value is defined in ems 0.0635em is subtracted (the equivalent of 1px in em using a 16px base).