DEV Community

Cover image for Media Queries and Responsive Design
Rashid Shamloo
Rashid Shamloo

Posted on

Media Queries and Responsive Design

Table of Contents

1. Introduction

With an ever-growing number of users 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. 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.

2. CSS

- Using min-width and max-width

By putting your CSS rules inside a @media block, they will only apply when the specified condition (Media Query) is met:

@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
   */
}
Enter fullscreen mode Exit fullscreen mode

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

@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
   */
}
Enter fullscreen mode Exit fullscreen mode

- Using the new range syntax

There is a newer range syntax you can use to more easily write your media queries:

@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
   */
}
Enter fullscreen mode Exit fullscreen mode

3. Styled-Components

You can use Media Queries in Styled-Components 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:

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

4. Tailwind CSS

Tailwind CSS has multiple pre-defined screen sizes (breakpoints) 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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

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
      },
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

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

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) { ... },
  },
}
Enter fullscreen mode Exit fullscreen mode

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

'md': { 'raw': '(min-width: 800px)' },
Enter fullscreen mode Exit fullscreen mode

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

module.exports = {
  theme: {
    screens: {
    // default screens will be replaced by this
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

module.exports = {
  theme: {
    extend: {
      screens: {
      // These will be applied in addition to the default screens
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Material UI

Material UI has its own set of breakpoints that can be customized in your theme. You can use them inside the sx prop to customize your styles for different screen sizes:

<Box
  sx={{ 
    color: { xs: 'white', md: 'black' },
  }}
/>
Enter fullscreen mode Exit fullscreen mode

By default, these will compile to min-width but Material UI also provides helpers you can use to target a range or use max-width instead:

<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',
    },
  })}
/>
Enter fullscreen mode Exit fullscreen mode

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() 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.

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
}
Enter fullscreen mode Exit fullscreen mode

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:

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

isLarge.addEventListener('change', () => {
  console.log(isLarge.matches);
});
Enter fullscreen mode Exit fullscreen mode

7. React

- window.matchMedia()

In React you can use the previously mentioned window.matchMedia() method:

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);
  }, []);
};
Enter fullscreen mode Exit fullscreen mode

- Custom Hooks

There are NPM packages like react-responsive that provide custom hooks for easy usage of Media Queries:

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

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

  useEffect(() => {
    console.log(isLarge);
  }, [isLarge]);
};
Enter fullscreen mode Exit fullscreen mode

Note: react-responsive uses matchmediaquery which itself uses window.matchMedia() under the hood.

If you're using Material UI, it also provides a custom useMediaQuery() hook you can use so you don't need to install any extra packages:

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

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

  useEffect(() => {
    console.log(isLarge);
  }, [isLarge]);
};
Enter fullscreen mode Exit fullscreen mode

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:

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.

9. Tips and Tricks

- 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() function.

#selector {
  font-size: clamp(1rem, 2.5vw, 2rem); 
}
Enter fullscreen mode Exit fullscreen mode

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.

- 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:

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.

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 property in addition to width, the clamp() function, 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.

Top comments (0)