Media Queries and Responsive Design
Tags: css, tutorial, react, javascript
Published: 2023-09-14 13:39:46

Table of Contents
- 1. [Introduction](#1)
- 2. [CSS](#2)
  - [Using min-width and max-width](#2-1)
  - [Using the new range syntax](#2-2)
- 3. [Styled-Components](#3)
- 4. [Tailwind CSS](#4)
- 5. [Material UI](#5)
- 6. [JavaScript](#6)
- 7. [React](#7)
  - [window.matchMedia()](#7-1)
  - [Custom Hooks](#7-2)
- 8. [An example](#8)
- 9. [Tips and Tricks](#9)
  - [Responsive font size](#9-1)
  - [Dealing with the impossible](#9-2)
- 10. [Conclusion](#10)

## <a name="1"></a>1. Introduction

With an [ever-growing number of users](https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/) using mobile devices to browse the internet, responsive design is a necessity.

However, The layout for a large horizontal screen (desktop) can be  quite different from the layout for a small vertical screen (mobile). We may need to change the flow of elements, resize or hide some elements, and maybe even change the number and position of elements displayed. 

We can achieve all that with [Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries). Media Queries allow us to apply different CSS rules for different screen sizes (and other device characteristics like the pointer type or orientation).

There are different ways of using Media Queries based on the CSS framework you're using. You can also use them in JavaScript or React to apply different logic for different screen sizes.

## <a name="2"></a>2. CSS

### <a name="2-1"></a>- Using min-width and max-width
By putting your CSS rules inside a [`@media`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media) block, they will only apply when the specified condition (Media Query) is met:

```css
@media screen and (min-width: 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *greater* than or equal to 768px
   */
}

@media screen and (max-width: 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *less* than or equal to 768px
   */
}
```

You can also use both `min-width` and `max-width` to target the screen sizes that fall in that range:

```css
@media screen and (min-width: 480px) and (max-width: 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *greater* than or equal to 480px and
   * is *less* than or equal to 768px
   */
}
```

### <a name="2-2"></a>- Using the new range syntax

There is a newer [range syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries#syntax_improvements_in_level_4) you can use to more easily write your media queries:

```css
@media screen and (width >= 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *greater* than or equal to 768px
   */
}

@media screen and (width <= 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *less* than or equal to 768px
   */
}

@media screen and (480px <= width <= 768px) {
  /* 
   * these rules will only apply when the screen width
   * is *greater* than or equal to 480px and
   * is *less* than or equal to 768px
   */
}
```

## <a name="3"></a>3. Styled-Components

You can use Media Queries in [Styled-Components](https://styled-components.com/) similar to how you would use them in CSS, other than the fact that you can define custom screen sizes in your theme and access them inside your Media Queries:

```js
const theme = {
  screen: {
    sm: '600px',
  },
}
```
``` js
const MyComponent = styled.div`
  @media screen and (min-width: ${({ theme }) => theme.screen.sm}) {
    ...
  }
`;
```

## <a name="4"></a>4. Tailwind CSS

[Tailwind CSS](https://tailwindcss.com/) has multiple pre-defined [screen sizes (breakpoints)](https://tailwindcss.com/docs/screens) that are compiled to the relevant Media Query (e.g. `md` will compile to `@media (min-width: 768px) { ... }`). You can use these breakpoints to easily apply different styles for different screen sizes:

```html
<div class="text-white md:text-black">
<!--
  The text color will be white for screen widths
  less than 768px and will be black for screen widths
  greater than or equal to 768px
-->
</div>
```

You can customize the pre-defined screen sizes or add new ones by modifying the `tailwind.config.js` file:

```js
module.exports = {
  theme: {
    extend: {
      screens: {
        'md': '800px',
        // 'md' will now be @media (min-width: 800px) { ... }
        'customName': '850px'
        // you can now use the new 'customName' breakpoint
      },
    },
  },
}
```

And if you want to use `max-width` instead of `min-width` or target a range, you can use the `max` and `min` properties:

```js
module.exports = {
  theme: {
      screens: {
        'md': {'max': '800px'},
        // 'md' will now be @media (max-width: 800px) { ... }
        'customName': {'min': '850px', 'max': '900px'},
        // the new 'customName' breakpoint will be
        // @media (min-width: 850px) and (max-width: 900px) { ... },
  },
}
```

You can also use the `raw` property to pass in a custom Media Query:

```js
'md': { 'raw': '(min-width: 800px)' },
```

> Note: When you add the `screens` directly to the `theme` object, it replaces the default `screens`:

```js
module.exports = {
  theme: {
    screens: {
    // default screens will be replaced by this
    }
  }
}
```

> If you want to add or modify a breakpoint while preserving the default `screens`, you should add your `screens` to the `theme.extend` object:

```js
module.exports = {
  theme: {
    extend: {
      screens: {
      // These will be applied in addition to the default screens
      }
    }
  }
}
```

## <a name="5"></a>5. Material UI

[Material UI](https://mui.com/) has its own set of [breakpoints](https://mui.com/material-ui/customization/breakpoints/#default-breakpoints) that can be [customized in your theme](https://mui.com/material-ui/customization/breakpoints/#custom-breakpoints). You can use them inside the [sx prop](https://mui.com/system/getting-started/the-sx-prop/) to customize your styles for different screen sizes:

```js
<Box
  sx={{ 
    color: { xs: 'white', md: 'black' },
  }}
/>
```

By default, these will compile to `min-width` but Material UI also provides [helpers](https://mui.com/material-ui/customization/breakpoints/#css-media-queries) you can use to target a range or use `max-width` instead:

```js
<Box
  sx={(theme) => ({ 
    [theme.breakpoints.down('md')]: {
      // @media (max-width: 900px)
      color: 'white',
    },
    [theme.breakpoints.between('md','lg')]: {
      // @media (min-width: 900px) and (max-width: 1200px)
      color: 'black',
    },
  })}
/>
```

## <a name="6"></a>6. JavaScript

Sometimes you want to know the screen size in your JavaScript code and change your logic based on it. You can use the [`window.matchMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) method for that.

`window.matchMedia()` returns an object with a `matches` property which is either `true` or `false` depending on whether the document matches the Media Query or not.

```js
const isLarge =  window.matchMedia("(min-width: 1024px)");

if (isLarge.matches) {
  // do something if screen width is larger than 1024px
} else {
  // do something else if it's not
}
```

However, the value is only calculated when you run your code for the first time. if you want to react to screen size changes (window resize), you need to add an event listener for the `change` event:

```js
const isLarge = window.matchMedia('(min-width: 1024px)');

isLarge.addEventListener('change', () => {
  console.log(isLarge.matches);
});
```

## <a name="7"></a>7. React

### <a name="7-1"></a>- window.matchMedia()

In React you can use the previously mentioned [`window.matchMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) method:

```ts
import { useEffect } from 'react';

const MyComponent = () => {
  const isLarge = window.matchMedia('(min-width: 1024px)');

  useEffect(() => {
    const handleChange = () => {
      console.log(isLarge.matches);
    };
    isLarge.addEventListener('change', handleChange);
    return () => isLarge.removeEventListener('change', handleChange);
  }, []);
};
```

### <a name="7-2"></a>- Custom Hooks

There are NPM packages like [react-responsive](https://www.npmjs.com/package/react-responsive) that provide custom hooks for easy usage of Media Queries:

```js
import { useEffect } from 'react';
import { useMediaQuery } from 'react-responsive'

const MyComponent = () => {
  const isLarge = useMediaQuery({
    query: '(min-width: 1024px)',
  });

  useEffect(() => {
    console.log(isLarge);
  }, [isLarge]);
};
```
> Note: [react-responsive](https://www.npmjs.com/package/react-responsive) uses [matchmediaquery](https://www.npmjs.com/package/matchmediaquery) which itself uses `window.matchMedia()` under the hood.

If you're using Material UI, it also provides a custom [`useMediaQuery()`](https://mui.com/material-ui/react-use-media-query/) hook you can use so you don't need to install any extra packages:

```js
import { useEffect } from 'react';
import { useMediaQuery } from '@mui/material/useMediaQuery'

const MyComponent = () => {
  const isLarge = useMediaQuery('(min-width: 1024px)');

  useEffect(() => {
    console.log(isLarge);
  }, [isLarge]);
};
```

## <a name="8"></a>8. An example

This is a small section of my portfolio that I'm working on atm, and I liked how customized/different I could make it look on various screen sizes so I decided to write this article to share my knowledge:

{% youtube epJOR93viT0 %}

In this example (ignoring the navigation menu and text) for different screen sizes:

- The size of the container element and images are changed
- The aspect ratio of the container element is changed while the images keep their aspect ratio
- The small balloon is hidden (`display: hidden`) on smaller screens
- The background images are moved (`left` and `top` property) to make a better composition for the new aspect ratio
- The bigger balloon is moved and also scaled down (`scale` property) on smaller screens to better fit in the frame.

## <a name="9"></a>9. Tips and Tricks

### <a name="9-1"></a>- Responsive font size

For adjusting the font size you don't necessarily need to use Media Queries. you can achieve a responsive font size using the [clamp()](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp) function.

```css
#selector {
  font-size: clamp(1rem, 2.5vw, 2rem); 
}
```

It sets the font size to the middle value (2.5vw) while limiting (clamping) it between the start (1rem) and end (2rem) values. resulting in a font size that grows with screen width (vw) but is limited to a minimum and maximum size.


### <a name="9-2"></a>- Dealing with the impossible

Sometimes the positioning or other properties of an element are so different on different screen sizes that you just can't achieve it by adjusting Flex/Grid properties or moving it around using absolute positioning.

In these situations, you can use two copies of the same element, position one perfectly for smaller screen sizes and one for the bigger ones. then hide one of them (`display: hidden;`) and only show the one that is suitable for the current screen size using Media Queries (breakpoints).

This is an example of this technique I used in [one of my projects](https://github.com/rashidshamloo/japan-slider):

{% youtube BVilewiGNDI %}

Notice how the information text/element is in a different location and also included in the tilt on bigger screen sizes but changes position and is not tilted on smaller screens. this is achieved by using two different elements and showing one and hiding the other based on the screen size.

> This is a similar technique to what you would use for toggling the display of mobile menu on your website just applied to two similar elements with different positions / properties.

## <a name="10"></a>10. Conclusion

Responsive design and its implementation takes much more than just using Media Queries.

You can show different image sizes or even totally different images, use the [max-width](https://developer.mozilla.org/en-US/docs/Web/CSS/max-width) property in addition to width, the [`clamp()`](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp) function, [`flex-wrap`](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap), and more to customize your design for various screen sizes.

It is also very easy to over-customize and use Media Queries when you don't need to, so you should always aim for the simplest solution because it'll be the most maintainable and won't break when a new device with a weird screen size enters the market.