react-media and react-responsive are both libraries designed to handle responsive design logic within React components, but they approach the problem from fundamentally different angles. react-media acts as a bridge to the browser's native window.matchMedia API, using a render prop pattern to conditionally render components based on live media query matches. It is tightly coupled to the browser environment and excels at complex, dynamic logic that CSS cannot easily express. react-responsive, on the other hand, provides a suite of components (like <Media>, <Desktop>, <Mobile>) that abstract media queries into declarative JSX. It focuses on simplicity and readability for standard breakpoint-based layouts, allowing developers to swap components based on screen size without writing raw media query strings.
Building responsive interfaces in React often leads to a fork in the road: do you handle breakpoints in CSS, or do you bring that logic into JavaScript? react-media and react-responsive both solve the latter, but they serve different architectural needs. One gives you direct access to the browser's matching engine, while the other offers a higher-level, declarative abstraction for common layout patterns.
The fundamental difference lies in how much control you need versus how much convenience you want.
react-media wraps the native window.matchMedia API. It does not guess what you want; it asks you to provide a specific media query string and tells you whether it matches. This makes it incredibly flexible for non-standard queries but requires you to manage the logic yourself.
// react-media: You define the exact query string
import Media from "react-media";
function Header() {
return (
<Media query="(max-width: 599px)">
{matches => matches ? <MobileHeader /> : <DesktopHeader />}
</Media>
);
}
react-responsive abstracts this away. Instead of writing query strings, you use pre-built components or named props like minWidth and maxWidth. It aims to make the JSX read like English sentences, hiding the underlying CSS syntax.
// react-responsive: You use descriptive props
import { Media } from "react-responsive";
function Header() {
return (
<Media maxWidth={599}>
{() => <MobileHeader />}
</Media>
);
}
// Or using the default export for complex ranges
import Media from "react-responsive";
<Media at="small">...</Media>
When your design requirements get specific—like targeting print media, high-DPI screens, or specific aspect ratios—the two libraries diverge sharply in usability.
react-media shines here because it accepts any valid CSS media query string. If the browser supports it, this library can query it. There is no learning curve for new CSS features; you just write the string.
// react-media: Handles complex, non-standard queries easily
<Media query="(orientation: landscape) and (min-resolution: 2dppx)">
{matches => matches ? <LandscapeRetinaView /> : <StandardView />}
</Media>
react-responsive requires you to map these complex concepts into its prop system. While it supports custom breakpoints, handling something like "print only" or "aspect ratio" often requires dropping down to its lower-level Media component with a custom query prop, which defeats the purpose of its abstraction.
// react-responsive: Requires specific props or fallback to query strings
<Media
query="(orientation: landscape) and (min-resolution: 2dppx)"
// The 'query' prop exists but feels less native to the library's intent
>
{() => <LandscapeRetinaView />}
</Media>
Both libraries historically relied on the "render prop" pattern (passing a function to children), but react-responsive has evolved to support more direct conditional rendering patterns that some developers find cleaner.
react-media strictly follows the render prop pattern. You must provide a function that receives the matches boolean. This forces you to explicitly handle the logic inside the function body.
// react-media: Strict render prop pattern
<Media query="(min-width: 768px)">
{matches => (
<div>
{matches ? <Sidebar /> : null}
<MainContent />
</div>
)}
</Media>
react-responsive allows you to use the children prop directly as the component to render if the match is true. This reduces boilerplate when you simply want to show or hide a single element.
// react-responsive: Direct children rendering
<Media minWidth={768}>
<Sidebar />
</Media>
// The Sidebar only renders if the condition is met
A critical architectural consideration for both packages is their behavior during Server-Side Rendering. Since neither library can access window or screen dimensions on the server, they face the same hydration challenge.
Both packages will typically render nothing or a default state on the server, then "flash" the correct content once the client hydrates and calculates the screen size. This can cause layout shifts.
For react-media, you often have to implement a custom defaultMatches prop or use a higher-order component to guess the initial state based on user-agent sniffing, as the library itself doesn't solve SSR out of the box.
// react-media: Manual SSR handling often required
<Media
query="(max-width: 768px)"
defaultMatches={false} // Guessing desktop first to avoid flash
>
{matches => <MobileNav />}
</Media>
react-responsive offers a ServerSideRendering context provider to help mitigate this. It allows you to set a global default match state for the entire app during the server pass, providing a slightly more structured way to handle the hydration mismatch.
// react-responsive: Built-in context for SSR
import { MediaContextProvider } from "react-responsive";
<MediaContextProvider deviceType="mobile">
<App />
</MediaContextProvider>
Before integrating either of these into a new greenfield project, you must consider their maintenance status. react-media is widely considered deprecated and unmaintained. The original repository has seen little activity for years, and it does not support modern React patterns (like Hooks) natively without wrappers. Using it introduces technical debt immediately.
react-responsive is actively maintained and continues to receive updates. It is the safer long-term bet for production applications.
In modern React development (React 16.8+), the community has largely moved away from Render Prop components like those used by both libraries in favor of Custom Hooks.
While react-responsive is still viable, many architects now prefer hooks like useMediaQuery (from mui or custom implementations) because they integrate better with functional components and avoid the "wrapper hell" of nested components.
// Modern Hook Approach (Preferred over both libraries today)
import { useMediaQuery } from "@mui/material";
function Header() {
const isMobile = useMediaQuery("(max-width: 599px)");
return isMobile ? <MobileHeader /> : <DesktopHeader />;
}
| Feature | react-media | react-responsive |
|---|---|---|
| Primary Approach | Raw matchMedia wrapper | Declarative breakpoint components |
| Query Syntax | CSS String (e.g., "(min-width: 500px)") | Props (e.g., minWidth={500}) |
| Flexibility | High (Any valid CSS query) | Medium (Best for standard breakpoints) |
| SSR Support | Manual / Difficult | Context Provider available |
| Maintenance Status | ⚠️ Deprecated / Unmaintained | ✅ Active |
| Best Use Case | Legacy projects or very specific non-standard queries | Standard responsive layouts in class/render-prop based codebases |
If you are starting a new project today, avoid react-media due to its deprecated status. While react-responsive is a solid, maintained library that simplifies breakpoint logic, the industry standard has shifted toward CSS-in-JS solutions (like Styled Components or Emotion) or Utility-First CSS (like Tailwind) where responsive design is handled in stylesheets, not JavaScript.
If you absolutely must handle responsive logic in JavaScript (for example, to load different heavy assets based on screen size), prefer custom hooks utilizing window.matchMedia directly, or use react-responsive if you need a quick, drop-in solution for existing class-based or render-prop heavy codebases. Do not start new architectures relying on the render-prop component pattern unless you have a specific constraint preventing the use of hooks.
Choose react-responsive if your primary goal is to cleanly separate UI variations for different screen sizes (e.g., mobile vs. desktop) using standard breakpoints. It is ideal for teams that prefer declarative JSX over writing raw media query strings and want built-in components for common device categories. This library is generally easier to read and maintain for typical layout shifts, though it still faces challenges with SSR since it needs to detect window dimensions on the client side.
Choose react-media if you need precise control over complex media logic that goes beyond simple breakpoints, such as handling orientation changes, aspect ratios, or print media specifically in JavaScript. It is the better choice when your responsive logic requires accessing the actual MediaQueryList object or when you need to integrate media states deeply into component behavior rather than just swapping UI elements. However, be aware that this package relies heavily on browser APIs, making it less suitable for server-side rendering (SSR) without significant customization.
| Package | react-responsive |
| Description | Media queries in react for responsive design |
| Browser Version | >= IE6* |
| Demo | |
The best supported, easiest to use react media query module.
$ npm install react-responsive --save
Hooks is a new feature available in 8.0.0!
import React from 'react'
import { useMediaQuery } from 'react-responsive'
const Example = () => {
const isDesktopOrLaptop = useMediaQuery({
query: '(min-width: 1224px)'
})
const isBigScreen = useMediaQuery({ query: '(min-width: 1824px)' })
const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1224px)' })
const isPortrait = useMediaQuery({ query: '(orientation: portrait)' })
const isRetina = useMediaQuery({ query: '(min-resolution: 2dppx)' })
return (
<div>
<h1>Device Test!</h1>
{isDesktopOrLaptop && <p>You are a desktop or laptop</p>}
{isBigScreen && <p>You have a huge screen</p>}
{isTabletOrMobile && <p>You are a tablet or mobile phone</p>}
<p>Your are in {isPortrait ? 'portrait' : 'landscape'} orientation</p>
{isRetina && <p>You are retina</p>}
</div>
)
}
import MediaQuery from 'react-responsive'
const Example = () => (
<div>
<h1>Device Test!</h1>
<MediaQuery minWidth={1224}>
<p>You are a desktop or laptop</p>
<MediaQuery minWidth={1824}>
<p>You also have a huge screen</p>
</MediaQuery>
</MediaQuery>
<MediaQuery minResolution="2dppx">
{/* You can also use a function (render prop) as a child */}
{(matches) =>
matches ? <p>You are retina</p> : <p>You are not retina</p>
}
</MediaQuery>
</div>
)
To make things more idiomatic to react, you can use camel-cased shorthands to construct media queries.
For a list of all possible shorthands and value types see https://github.com/yocontra/react-responsive/blob/master/src/mediaQuery.ts#L9.
Any numbers given as shorthand will be expanded to px (1234 will become '1234px').
The CSS media queries in the example above could be constructed like this:
import React from 'react'
import { useMediaQuery } from 'react-responsive'
const Example = () => {
const isDesktopOrLaptop = useMediaQuery({ minWidth: 1224 })
const isBigScreen = useMediaQuery({ minWidth: 1824 })
const isTabletOrMobile = useMediaQuery({ maxWidth: 1224 })
const isPortrait = useMediaQuery({ orientation: 'portrait' })
const isRetina = useMediaQuery({ minResolution: '2dppx' })
return <div>...</div>
}
device propAt times you may need to render components with different device settings than what gets automatically detected. This is especially useful in a Node environment where these settings can't be detected (SSR) or for testing.
orientation, scan, aspectRatio, deviceAspectRatio,
height, deviceHeight, width, deviceWidth, color, colorIndex, monochrome,
resolution and type
type can be one of: all, grid, aural, braille, handheld, print, projection,
screen, tty, tv or embossed
Note: The device property always applies, even when it can be detected (where window.matchMedia exists).
import { useMediaQuery } from 'react-responsive'
const Example = () => {
const isDesktopOrLaptop = useMediaQuery(
{ minDeviceWidth: 1224 },
{ deviceWidth: 1600 } // `device` prop
)
return (
<div>
{isDesktopOrLaptop && (
<p>
this will always get rendered even if device is shorter than 1224px,
that's because we overrode device settings with 'deviceWidth: 1600'.
</p>
)}
</div>
)
}
You can also pass device to every useMediaQuery hook in the components tree through a React Context.
This should ease up server-side-rendering and testing in a Node environment, e.g:
import { Context as ResponsiveContext } from 'react-responsive'
import { renderToString } from 'react-dom/server'
import App from './App'
...
// Context is just a regular React Context component, it accepts a `value` prop to be passed to consuming components
const mobileApp = renderToString(
<ResponsiveContext.Provider value={{ width: 500 }}>
<App />
</ResponsiveContext.Provider>
)
...
If you use next.js, structure your import like this to disable server-side rendering for components that use this library:
import dynamic from 'next/dynamic'
const MediaQuery = dynamic(() => import('react-responsive'), {
ssr: false
})
import { Context as ResponsiveContext } from 'react-responsive'
import { render } from '@testing-library/react'
import ProductsListing from './ProductsListing'
describe('ProductsListing', () => {
test('matches the snapshot', () => {
const { container: mobile } = render(
<ResponsiveContext.Provider value={{ width: 300 }}>
<ProductsListing />
</ResponsiveContext.Provider>
)
expect(mobile).toMatchSnapshot()
const { container: desktop } = render(
<ResponsiveContext.Provider value={{ width: 1000 }}>
<ProductsListing />
</ResponsiveContext.Provider>
)
expect(desktop).toMatchSnapshot()
})
})
Note that if anything has a device prop passed in it will take precedence over the one from context.
onChangeYou can use the onChange callback to specify a change handler that will be called when the media query's value changes.
import React from 'react'
import { useMediaQuery } from 'react-responsive'
const Example = () => {
const handleMediaQueryChange = (matches) => {
// matches will be true or false based on the value for the media query
}
const isDesktopOrLaptop = useMediaQuery(
{ minWidth: 1224 },
undefined,
handleMediaQueryChange
)
return <div>...</div>
}
import React from 'react'
import MediaQuery from 'react-responsive'
const Example = () => {
const handleMediaQueryChange = (matches) => {
// matches will be true or false based on the value for the media query
}
return (
<MediaQuery minWidth={1224} onChange={handleMediaQueryChange}>
...
</MediaQuery>
)
}
That's it! Now you can create your application specific breakpoints and reuse them easily. Here is an example:
import { useMediaQuery } from 'react-responsive'
const Desktop = ({ children }) => {
const isDesktop = useMediaQuery({ minWidth: 992 })
return isDesktop ? children : null
}
const Tablet = ({ children }) => {
const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 991 })
return isTablet ? children : null
}
const Mobile = ({ children }) => {
const isMobile = useMediaQuery({ maxWidth: 767 })
return isMobile ? children : null
}
const Default = ({ children }) => {
const isNotMobile = useMediaQuery({ minWidth: 768 })
return isNotMobile ? children : null
}
const Example = () => (
<div>
<Desktop>Desktop or laptop</Desktop>
<Tablet>Tablet</Tablet>
<Mobile>Mobile</Mobile>
<Default>Not mobile (desktop or laptop or tablet)</Default>
</div>
)
export default Example
And if you want a combo (the DRY way):
import { useMediaQuery } from 'react-responsive'
const useDesktopMediaQuery = () =>
useMediaQuery({ query: '(min-width: 1280px)' })
const useTabletAndBelowMediaQuery = () =>
useMediaQuery({ query: '(max-width: 1279px)' })
const Desktop = ({ children }) => {
const isDesktop = useDesktopMediaQuery()
return isDesktop ? children : null
}
const TabletAndBelow = ({ children }) => {
const isTabletAndBelow = useTabletAndBelowMediaQuery()
return isTabletAndBelow ? children : null
}
| Chrome | 9 |
| Firefox (Gecko) | 6 |
| MS Edge | All |
| Internet Explorer | 10 |
| Opera | 12.1 |
| Safari | 5.1 |
Pretty much everything. Check out these polyfills: