Skip to content

bloczjs/react-responsive

Repository files navigation

@blocz/react-responsive

@blocz/react-responsive is inspired by the .visible classes from bootstrap 4 (and .hidden classes from bootstrap 3): it lets you show or hide components based on the current screen size.

See changelog

Table of contents

  1. Installation
  2. How to use
    1. useMediaQuery()
    2. Media ranges
      1. Default media ranges
      2. Additional Up and Down
      3. useMediaRange()
      4. <Only>
        1. on prop
        2. matchMedia
        3. Render as component (deprecated)
      5. Custom media ranges: createMediaRanges()
        1. Strictly typed
        2. Stricter <Only>
        3. Units & direction
      6. <MediaRangesProvider> (deprecated)
        1. Add more media ranges
        2. Change default media ranges
        3. Units
        4. Direction
  3. Comparison to other libraries
  4. matchMedia polyfill
    1. Browser
    2. Node
  5. React 16 / 17 support
  6. Deprecated APIs
  7. FAQ

Installation

# pnpm
pnpm add @blocz/react-responsive

# yarn
yarn add @blocz/react-responsive

# npm
npm install @blocz/react-responsive

How to use

useMediaQuery()

useMediaQuery() is a hook that detects if the given media query matches the current viewport.

import React from "react";
import { useMediaQuery } from "@blocz/react-responsive";

const App = () => {
  const isLandscape = useMediaQuery("(orientation: landscape)");
  return <p>{isLandscape ? "Landscape mode" : "Portrait mode"}</p>;
};

Learn more about CSS media queries

Media ranges

@blocz/react-responsive is based on the classic bootstrap breakpoints: xs, sm, md, lg and xl.

Unlike Bootstrap β€” where xs, sm, etc. are single breakpoints β€” @blocz/react-responsive uses media ranges: each name describes the interval between two breakpoints, making ranges explicit and non-overlapping.

See Why media ranges instead of breakpoints? for more context.

Default media ranges

By default, the media ranges are:

Media range From To
xs 0px 575px
sm 576px 767px
md 768px 991px
lg 992px 1199px
xl 1200px Infinity

This makes it fully explicit: a lg device is not md nor xl.

Additional Up and Down

Each media range also comes with {mediaRange}Up and {mediaRange}Down variants β€” covering everything above or below that breakpoint:

Media range From To
xsUp 0px Infinity
smUp 576px Infinity
mdUp 768px Infinity
lgUp 992px Infinity
xlUp 1200px Infinity
Media range From To
xsDown 0px 575px
smDown 0px 767px
mdDown 0px 991px
lgDown 0px 1199px
xlDown 0px Infinity

Note: xsDown is equivalent to xs, xlUp is equivalent to xl, and xlDown/xsUp match all screen sizes β€” these exist only for convenience.

useMediaRange()

useMediaRange() is a hook that detects if the given media range matches the current viewport.

import React from "react";
import { useMediaRange } from "@blocz/react-responsive";

const App = () => {
  const matchXl = useMediaRange("xl");
  const matchMdDown = useMediaRange("mdDown");
  const matchMdOrLg = useMediaRange("md lg");
  return (
    <ul>
      {matchXl && <li>Visible on every "large" device</li>}
      {matchMdDown && <li>Visible on every device smaller than or equal to "medium"</li>}
      {matchMdOrLg && <li>Visible on every "medium" or "large" device</li>}
    </ul>
  );
};

<Only>

<Only> is the component equivalent of useMediaRange() and useMediaQuery(): it renders its children only when the condition matches.

on prop

The on prop behaves like useMediaRange(): it accepts a media range name (or a space-separated list of names) and makes <Only> render its children when any of the named ranges match.

import React from "react";
import { Only } from "@blocz/react-responsive";

const App = () => (
  <React.Fragment>
    <Only on="xs">Only visible for extra small devices (portrait phones)</Only>
    <Only on="sm">Only visible for small devices (landscape phones)</Only>
    <Only on="md">Only visible for medium devices (tablets)</Only>
    <Only on="lg">Only visible for large devices (desktops)</Only>
    <Only on="xl">Only visible for extra large devices (large desktops)</Only>
    <Only on="sm xl">Only visible for small AND extra large devices</Only>
  </React.Fragment>
);
matchMedia

The matchMedia prop behaves like useMediaQuery(): it accepts any regular query supported by window.matchMedia.

import React from "react";
import { Only } from "@blocz/react-responsive";

const App = () => (
  <Only matchMedia="(min-device-width: 500px) and (orientation: landscape)">
    Visible on every device bigger than "500px" and in landscape mode
  </Only>
);

Note: If you use on AND matchMedia together, the component renders if any of the media ranges matches OR if the media query is fulfilled (not AND).

Render as component (deprecated)

⚠️ Using the as prop on <Only> is deprecated and will be removed in v6.0.0. This is not considered as type-safe

The as prop makes <Only> render as a different element (any DOM tag or React component). Any props except on, matchMedia, and as are forwarded to it:

import React from "react";
import { Only } from "@blocz/react-responsive";

const App = () => (
  <ul>
    <Only as="li" on="xs">
      Only visible for extra small devices
    </Only>
    <Only as="li" on="sm">
      Only visible for small devices
    </Only>
    <Only as="li" on="md">
      Only visible for medium devices
    </Only>
  </ul>
);

The as prop also accepts React components:

import React from "react";
import { Only } from "@blocz/react-responsive";

const Custom = ({ title, children }) => (
  <React.Fragment>
    <h3>{title}</h3>
    <p>{children}</p>
  </React.Fragment>
);

const App = () => (
  <React.Fragment>
    <Only as={Custom} title="xs" on="xs">
      Only visible for extra small devices
    </Only>
    <Only as={Custom} title="sm" on="sm">
      Only visible for small devices
    </Only>
    <Only as={Custom} title="md" on="md">
      Only visible for medium devices
    </Only>
  </React.Fragment>
);

Custom media ranges: createMediaRanges()

createMediaRanges() is the recommended way to customize the media ranges. It returns an object { useMediaRange, Only } bound to the ranges you pass in, with end-to-end TypeScript types.

import { createMediaRanges, DEFAULT_MEDIA_RANGES } from "@blocz/react-responsive";

const { useMediaRange, Only } = createMediaRanges({
  ...DEFAULT_MEDIA_RANGES,
  pxRange: [263, 863, { unit: "px" }],
  emRange: [20, 40, { unit: "em" }],
});

If you want to re-use the same defaults as the top-level <Only> & useMediaRange(), you'll need to import & use DEFAULT_MEDIA_RANGES.

Strictly typed

The returned useMediaRange() accepts only the names that match the ranges you declared (plus the auto-generated Up and Down aliases). The passed string can hold a single name or a space-separated list, every media range will be typechecked:

useMediaRange("md"); // βœ…
useMediaRange("pxRangeUp"); // βœ…
useMediaRange("mdDown"); // βœ…
useMediaRange("md pxRange"); // βœ…
useMediaRange("invalid"); // ❌ TS error
useMediaRange("md invalid"); // ❌ TS error – "md" is fine, "invalid" is not

This is also true for the returned <Only>:

<>
  <Only
    // βœ…
    on="md pxRange"
  >
    …
  </Only>

  <Only
    // ❌ TS error
    on="lg invalid"
  >
    …
  </Only>
</>
Stricter <Only>

Unlike the top-level <Only>, the <Only> returned from createMediaRanges() does not support the as prop (and does not forward additional props to an inner element).

Units & direction

Each range entry accepts one of these shapes: [min, max], or [min, max, { unit?, direction? }]:

const { Only } = createMediaRanges({
  pxRange: [263, 863, { unit: "px" }],
  emRange: [20, 40, { unit: "em" }],
  yRange: [200, 400, { direction: "height" }],
});

<MediaRangesProvider> (deprecated)

⚠️ <MediaRangesProvider> is deprecated and will be removed in v6.0.0. Use createMediaRanges() instead.

<MediaRangesProvider> defines all media range values.

Use it to inject or modify the media ranges (only use one <MediaRangesProvider> per build).

Add more media ranges
import React from "react";
import { Only, MediaRangesProvider } from "@blocz/react-responsive";

const App = () => (
  <MediaRangesProvider additionalMediaRanges={{ customRange: [263, 863] }}>
    <Only on="customRange">Visible on every device from "263px" to "863px"</Only>
    <Only on="customRangeUp">Visible on every device bigger than "263px"</Only>
    <Only on="customRangeDown">Visible on every device smaller than "863px"</Only>
  </MediaRangesProvider>
);
Change default media ranges
import React from "react";
import { Only, MediaRangesProvider } from "@blocz/react-responsive";

const App = () => (
  <MediaRangesProvider mediaRanges={{ sm: [263, 863] }}>
    <Only on="sm">Visible on every device from "263px" to "863px"</Only>
    <Only on="smUp">Visible on every device bigger than "263px"</Only>
    <Only on="smDown">Visible on every device smaller than "863px"</Only>
  </MediaRangesProvider>
);

Warning: This overrides completely the default media ranges, in this example, the other media ranges xs, md, lg and xl are no longer defined!

Units

You can specify which unit is going to be used for the media range by specifying in the 3rd option a "unit" key.

Every CSS unit is supported. The default unit is px.

import React from "react";
import { Only, MediaRangesProvider } from "@blocz/react-responsive";

const App = () => (
  <MediaRangesProvider
    additionalMediaRanges={{
      pxRange: [263, 863, { unit: "px" }],
      emRange: [20, 40, { unit: "em" }],
    }}
  >
    <Only on="pxRange">Visible on every device from "263px" to "863px"</Only>
    <Only on="emRange">Visible on every device from "20em" to "40em"</Only>
  </MediaRangesProvider>
);
Direction

You can specify which direction is used for the media queries (height or width).

By default, "width" is the chosen direction.

import React from "react";
import { Only, MediaRangesProvider } from "@blocz/react-responsive";

const App = () => (
  <MediaRangesProvider
    mediaRanges={{
      xRange: [300, 500, { direction: "width" }],
      yRange: [200, 400, { direction: "height" }],
    }}
  >
    <Only on="xRange">Visible on every device from "300px" to "500px" wide</Only>
    <Only on="yRange">Visible on every device from "200px" to "400px" tall</Only>
  </MediaRangesProvider>
);

Comparison to other libraries

Lib Media ranges Custom media ranges Media query matchMedia listener* hooks SSR support
@blocz/react-responsive βœ… βœ… βœ… βœ… βœ… βœ…
react-responsive ❌ ❌ βœ… βœ… βœ… βœ…
react-breakpoints βœ… βœ… ❌ ❌ ❌ βœ…
react-responsive-breakpoints βœ… ❌ ❌ ❌ ❌ ❌

*: matchMedia listener event means that the library is built around matchMedia.addListener(callback) and not window.addEventListener('resize', callback) (which is faster because the callback is only triggered when the media query's state changes and not at every resize).

matchMedia polyfill

Browser

If you want to use matchMedia in browsers that don't support it, I'd recommend matchmedia-polyfill.

Node

If you want to mock matchMedia on Node to execute tests for instance, you can use mock-match-media.

And if you need an example with Jest, @testing-library/react, React and @blocz/react-responsive, you can take a look at these tests.

React 16 / 17 support

@blocz/react-responsive relies on useSyncExternalStore. This function was added in React 18. If you are on React 16.8+ / React 17, you'll need to use use-sync-external-store to polyfill useSyncExternalStore.

Deprecated APIs

The terminology used by this library used to be "breakpoint". It was renamed to "media range" because each entry actually describes the range between two breakpoints rather than a single breakpoint.

For backward compatibility, the previous exports are still available but marked as @deprecated, and will be removed in the next major release:

Deprecated Replacement
useBreakpoint() useMediaRange()
<BreakpointsProvider> <MediaRangesProvider>
<BreakpointsContext> <MediaRangesContext>
breakpoints prop mediaRanges prop
additionalBreakpoints prop additionalMediaRanges prop
<MediaRangesProvider> createMediaRanges()
<MediaRangesContext> createMediaRanges()

FAQ

For other questions, please take a look at our FAQ document.

About

πŸ” <Only /> displays some contents for particular screen sizes

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors