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
*/
}
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
*/
}
- 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
*/
}
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',
},
}
const MyComponent = styled.div`
@media screen and (min-width: ${({ theme }) => theme.screen.sm}) {
...
}
`;
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>
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
},
},
},
}
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) { ... },
},
}
You can also use the raw
property to pass in a custom Media Query:
'md': { 'raw': '(min-width: 800px)' },
Note: When you add the
screens
directly to thetheme
object, it replaces the defaultscreens
:
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 yourscreens
to thetheme.extend
object:
module.exports = {
theme: {
extend: {
screens: {
// These will be applied in addition to the default screens
}
}
}
}
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' },
}}
/>
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',
},
})}
/>
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
}
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);
});
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);
}, []);
};
- 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]);
};
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]);
};
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 screensThe background images are moved (
left
andtop
property) to make a better composition for the new aspect ratioThe 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);
}
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.