Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
240 lines
8.2 KiB
Markdown
240 lines
8.2 KiB
Markdown
---
|
||
productId: material-ui
|
||
title: Media queries in React for responsive design
|
||
githubLabel: 'hook: useMediaQuery'
|
||
githubSource: packages/mui-material/src/useMediaQuery
|
||
---
|
||
|
||
# useMediaQuery
|
||
|
||
<p class="description">This React hook listens for matches to a CSS media query. It allows the rendering of components based on whether the query matches or not.</p>
|
||
|
||
Some of the key features:
|
||
|
||
- ⚛️ It has an idiomatic React API.
|
||
- 🚀 It's performant, it observes the document to detect when its media queries change, instead of polling the values periodically.
|
||
- 📦 [1.1 kB gzipped](https://bundlephobia.com/package/@mui/material).
|
||
- 🤖 It supports server-side rendering.
|
||
|
||
{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
|
||
|
||
## Basic media query
|
||
|
||
You should provide a media query to the first argument of the hook.
|
||
The media query string can be any valid CSS media query, for example [`'(prefers-color-scheme: dark)'`](/material-ui/customization/dark-mode/#system-preference).
|
||
|
||
{{"demo": "SimpleMediaQuery.js", "defaultCodeOpen": true}}
|
||
|
||
:::warning
|
||
Using the query `'print'` to modify a document for printing is not supported, as changes made in re-rendering may not be accurately reflected.
|
||
You can use the `sx` prop's `displayPrint` field for this purpose instead.
|
||
See [MUI System—Display in print](/system/display/#display-in-print) for more details.
|
||
:::
|
||
|
||
## Using breakpoint helpers
|
||
|
||
You can use Material UI's [breakpoint helpers](/material-ui/customization/breakpoints/) as follows:
|
||
|
||
```jsx
|
||
import { useTheme } from '@mui/material/styles';
|
||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||
|
||
function MyComponent() {
|
||
const theme = useTheme();
|
||
const matches = useMediaQuery(theme.breakpoints.up('sm'));
|
||
|
||
return <span>{`theme.breakpoints.up('sm') matches: ${matches}`}</span>;
|
||
}
|
||
```
|
||
|
||
{{"demo": "ThemeHelper.js", "defaultCodeOpen": false}}
|
||
|
||
Alternatively, you can use a callback function, accepting the theme as a first argument:
|
||
|
||
```jsx
|
||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||
|
||
function MyComponent() {
|
||
const matches = useMediaQuery((theme) => theme.breakpoints.up('sm'));
|
||
|
||
return <span>{`theme.breakpoints.up('sm') matches: ${matches}`}</span>;
|
||
}
|
||
```
|
||
|
||
⚠️ There is **no default** theme support, you have to inject it in a parent theme provider.
|
||
|
||
## Using JavaScript syntax
|
||
|
||
You can use [json2mq](https://github.com/akiran/json2mq) to generate media query string from a JavaScript object.
|
||
|
||
{{"demo": "JavaScriptMedia.js", "defaultCodeOpen": true}}
|
||
|
||
## Testing
|
||
|
||
You need an implementation of [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) in your test environment.
|
||
|
||
For instance, [jsdom doesn't support it yet](https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom). You should polyfill it.
|
||
Using [css-mediaquery](https://github.com/ericf/css-mediaquery) to emulate it is recommended.
|
||
|
||
```js
|
||
import mediaQuery from 'css-mediaquery';
|
||
|
||
function createMatchMedia(width) {
|
||
return (query) => ({
|
||
matches: mediaQuery.match(query, {
|
||
width,
|
||
}),
|
||
addEventListener: () => {},
|
||
removeEventListener: () => {},
|
||
});
|
||
}
|
||
|
||
describe('MyTests', () => {
|
||
beforeAll(() => {
|
||
window.matchMedia = createMatchMedia(window.innerWidth);
|
||
});
|
||
});
|
||
```
|
||
|
||
## Client-side only rendering
|
||
|
||
To perform the server-side hydration, the hook needs to render twice.
|
||
A first time with `defaultMatches`, the value of the server, and a second time with the resolved value.
|
||
This double pass rendering cycle comes with a drawback: it's slower.
|
||
You can set the `noSsr` option to `true` if you use the returned value **only** client-side.
|
||
|
||
```js
|
||
const matches = useMediaQuery('(min-width:600px)', { noSsr: true });
|
||
```
|
||
|
||
or it can turn it on globally with the theme:
|
||
|
||
```js
|
||
const theme = createTheme({
|
||
components: {
|
||
MuiUseMediaQuery: {
|
||
defaultProps: {
|
||
noSsr: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
:::info
|
||
Note that `noSsr` has no effects when using the `createRoot()` API (the client-side only API introduced in React 18).
|
||
:::
|
||
|
||
## Server-side rendering
|
||
|
||
:::warning
|
||
Server-side rendering and client-side media queries are fundamentally at odds.
|
||
Be aware of the tradeoff. The support can only be partial.
|
||
:::
|
||
|
||
Try relying on client-side CSS media queries first.
|
||
For instance, you could use:
|
||
|
||
- [`<Box display>`](/system/display/#hiding-elements)
|
||
- [`themes.breakpoints.up(x)`](/material-ui/customization/breakpoints/#css-media-queries)
|
||
- or [`sx prop`](/system/getting-started/the-sx-prop/)
|
||
|
||
If none of the above alternatives are an option, you can proceed reading this section of the documentation.
|
||
|
||
First, you need to guess the characteristics of the client request, from the server.
|
||
You have the choice between using:
|
||
|
||
- **User agent**. Parse the user agent string of the client to extract information. Using [ua-parser-js](https://github.com/faisalman/ua-parser-js) to parse the user agent is recommended.
|
||
- **Client hints**. Read the hints the client is sending to the server. Be aware that this feature is [not supported everywhere](https://caniuse.com/#search=client%20hint).
|
||
|
||
Finally, you need to provide an implementation of [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) to the `useMediaQuery` with the previously guessed characteristics.
|
||
Using [css-mediaquery](https://github.com/ericf/css-mediaquery) to emulate matchMedia is recommended.
|
||
|
||
For instance on the server-side:
|
||
|
||
```js
|
||
import * as ReactDOMServer from 'react-dom/server';
|
||
import parser from 'ua-parser-js';
|
||
import mediaQuery from 'css-mediaquery';
|
||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||
|
||
function handleRender(req, res) {
|
||
const deviceType = parser(req.headers['user-agent']).device.type || 'desktop';
|
||
const ssrMatchMedia = (query) => ({
|
||
matches: mediaQuery.match(query, {
|
||
// The estimated CSS width of the browser.
|
||
width: deviceType === 'mobile' ? '0px' : '1024px',
|
||
}),
|
||
});
|
||
|
||
const theme = createTheme({
|
||
components: {
|
||
// Change the default options of useMediaQuery
|
||
MuiUseMediaQuery: {
|
||
defaultProps: {
|
||
ssrMatchMedia,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
const html = ReactDOMServer.renderToString(
|
||
<ThemeProvider theme={theme}>
|
||
<App />
|
||
</ThemeProvider>,
|
||
);
|
||
|
||
// …
|
||
}
|
||
```
|
||
|
||
{{"demo": "ServerSide.js", "defaultCodeOpen": false}}
|
||
|
||
Make sure you provide the same custom match media implementation to the client-side to guarantee a hydration match.
|
||
|
||
## Migrating from `withWidth()`
|
||
|
||
The `withWidth()` higher-order component injects the screen width of the page.
|
||
You can reproduce the same behavior with a `useWidth` hook:
|
||
|
||
{{"demo": "UseWidth.js"}}
|
||
|
||
## API
|
||
|
||
### `useMediaQuery(query, [options]) => matches`
|
||
|
||
#### Arguments
|
||
|
||
1. `query` (_string_ | _func_): A string representing the media query to handle or a callback function accepting the theme (in the context) that returns a string.
|
||
2. `options` (_object_ [optional]):
|
||
|
||
- `options.defaultMatches` (_bool_ [optional]):
|
||
As `window.matchMedia()` is unavailable on the server,
|
||
it returns a default matches during the first mount. The default value is `false`.
|
||
- `options.matchMedia` (_func_ [optional]): You can provide your own implementation of _matchMedia_. This can be used for handling an iframe content window.
|
||
- `options.noSsr` (_bool_ [optional]): Defaults to `false`.
|
||
To perform the server-side hydration, the hook needs to render twice.
|
||
A first time with `defaultMatches`, the value of the server, and a second time with the resolved value.
|
||
This double pass rendering cycle comes with a drawback: it's slower.
|
||
You can set this option to `true` if you use the returned value **only** client-side.
|
||
- `options.ssrMatchMedia` (_func_ [optional]): You can provide your own implementation of _matchMedia_, it's used when rendering server-side.
|
||
|
||
Note: You can change the default options using the [`default props`](/material-ui/customization/theme-components/#theme-default-props) feature of the theme with the `MuiUseMediaQuery` key.
|
||
|
||
#### Returns
|
||
|
||
`matches`: Matches is `true` if the document currently matches the media query and `false` when it does not.
|
||
|
||
#### Examples
|
||
|
||
```jsx
|
||
import * as React from 'react';
|
||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||
|
||
export default function SimpleMediaQuery() {
|
||
const matches = useMediaQuery('(min-width:600px)');
|
||
|
||
return <span>{`(min-width:600px) matches: ${matches}`}</span>;
|
||
}
|
||
```
|