Introduction
You’ve likely used media queries to adapt your website for different devices, and you might have written something like:
@media only screen and (max-width: 768px) {
/* ... */
}
But did you know that the capabilities of @media
go far beyond this? Below, we’ll explore some interesting and unusual ways to use `@media` but first, let’s briefly review the basics of media queries.
If you’re already familiar with the basics, feel free to skip ahead to the unusual ways to use media queries.
Basics of @media
and Media Queries
@media
and media queries are often thought to be the same thing, but there’s a slight difference:
@media
is an at-rule used to declare a media query. It applies specific styles only when the specified media query conditions are met.- A media query is the condition inside
@media
that checks certain parameters of the current device (like width, height, orientation, etc.). - An at-rule in CSS is a command that starts with
@
and is used to control the behavior of styles. at-rules allow CSS to provide special instructions to the browser. Besides@media
, there are other, such as:@import
— allows importing other CSS files.@font-face
— used to declare custom fonts.@keyframes
— defines animations with keyframes.@container
— allows styling based on the size or other properties of an element’s container.
Returning to our example:
@media only screen and (max-width: 768px) {
/* ... */
}
@media
is the at-rule.only screen and (max-width: 768px)
is the media query.
Each media query can include:
-
Media types (<media-type>) — types of devices the styles apply to, like:
screen
: for screens (computers, phones, tablets).print
: for printed documents.all
: for all media types.
-
Media features (<media-feature>) — device characteristics checked in media queries to apply styles, like:
width
: viewport width.height
: viewport height.orientation
: screen orientation (portrait
orlandscape
).
-
Logical operators (Logical operators) — combine media types and media features in one query:
and
: for conditions that all must be met.or
: for at least one condition to be met.not
: to exclude certain conditions.only
: limits styles to specific media types or conditions, ignoring compatibility with older browsers.,
(comma): to combine multiple media queries (similar to logical “or”).
Now, let’s break down the media query from our example:
@media only screen and (max-width: 768px) {
/* ... */
}
-
only
— limits style application to devices that support media queries. This was previously relevant, as older browsers that didn’t support media queries would interpretscreen and (max-width: 768px)
asscreen
, ignoring the rest of the query and applying the styles on all screens. However, it’s worth noting that when we say “old browsers,” these are indeed very old (like Internet Explorer 8 and below). So, in modern code,only
can be safely omitted. -
screen
— the media type indicating that styles apply only to screen devices (like monitors, smartphones, tablets). -
and
— the logical operator used to combine multiple media features in one query. In this case, it combines the media type and the media feature. -
(max-width: 768px)
— the media feature indicating that styles will only apply to screens with a width up to 768 pixels.
Overall, this media query restricts styles to screens with a width of up to 768 pixels.
The keyword only
can be considered a relic of the past, so the query can be simplified to:
@media screen and (max-width: 768px) {
/* ... */
}
Unusual Ways to Use Media Queries
Besides the usual use of media queries in CSS, as shown in the examples above, there are other equally useful methods:
@import
You can combine a media query with @import
for conditionally loading CSS files. For example:
@import url('styles.css') screen and (min-width: 768px);
HTML
The media
attribute can be used in HTML elements such as:
<link>
<link rel="stylesheet" href="styles.css" media="screen and (max-width: 768px)" />
- In this case, if the screen width is less than or equal to
768px
, the styles fromstyles.css
will be loaded and applied. - However, if the screen width is greater than
768px
,styles.css
will still be loaded (without render blocking), but the styles will not be applied.
Let’s repeat it:
- If the media query in the
media
attribute evaluates totrue
(in this case, if the width is less than or equal to768px
), the.css
file and its contents will be loaded with render blocking, and its styles will be applied as usual. - But if the media query evaluates to
false
, the file will be loaded without render blocking, and the styles won’t be applied.
<style>
<style media="screen and (max-width: 768px)">
/* ... */
</style>
- If the media query matches, the styles inside the
<style>
element will be applied, otherwise, they won’t.
<source>
<picture>
<source srcset="image.webp" type="image/webp" media="(min-width: 768px)" />
<img src="image.jpg" alt="An image" />
</picture>
- If the media query matches,
image.webp
is used; otherwise,image.jpg
is used.
The sizes
Attribute
Apart from the media
attribute in the examples above, media queries are also used in the sizes
attribute to specify the image size in <img>
or <source>
. For example:
<img
src="image.jpg"
alt="An image"
sizes="(max-width: 768px) 100vw, 50vw"
srcset="image-400.jpg 400w, image-800.jpg 800w"
/>
-
The
sizes
attribute helps the browser determine how wide the image should be based on the media query conditions. -
Here, if the screen width is
768px
or less, the image will take up the full viewport width (100vw
), while for larger screens, it will take up half of the viewport width (50vw
).
window.matchMedia
Finally, you can use JavaScript to evaluate media queries. This can be done using the window.matchMedia
method, which returns a MediaQueryList object that can be used for:
-
Instant checking: determining if the document matches a given media query (
.matches
). -
Monitoring changes: reacting when the document starts or stops matching a media query (
.addListener
).
You can see an example of its use in the prefers-color-scheme section below.
Unusual Media Features
After understanding the basics of media queries and all the ways to use them, let’s take a look at some interesting media features. Besides the standard ones — like width, orientation, etc. — there is a whole range of media features that can come in handy.
The full list can be found here, and we’ll focus on the most interesting ones, in my opinion.
prefers-color-scheme
- Allows you to determine whether the user prefers a light or dark theme on the current device.
Here is the simplest way to use this feature:
body {
color: black;
background-color: white;
}
@media (prefers-color-scheme: dark) {
body {
color: white;
background-color: black;
}
}
However, websites or applications that support both light and dark themes usually also provide a theme selectors. A perfect example is the blog site you’re currently reading.
By default, the theme is determined based on prefers-color-scheme
, and then, if you decide to change it, your selection is saved in local storage
, and the theme will be applied based on your previous choice.
In code, it might look like this:
type Theme = 'light' | 'dark';
function getTheme(): Theme {
const localStorageTheme = localStorage.getItem('theme');
if (localStorageTheme === 'light' || localStorageTheme === 'dark') {
return localStorageTheme;
} else {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
}
Note the use of the window.matchMedia
function, which we mentioned earlier.
pointer
/ any-pointer
pointer
and any-pointer
allow you to determine whether the user has a pointing device (e.g., a mouse or stylus) and, if so, how precise that device is. This makes it possible to adapt the styles and interactions on the page to suit the capabilities of the device.
Possible values:
none
: No pointer available.coarse
: A pointer with limited accuracy, such as a finger on a touchscreen.fine
: A precise pointer, such as a mouse or stylus.
But what is the difference between pointer
and any-pointer
?
The difference is that pointer
takes into account only the primary input mechanism, while any-pointer
considers any input device.
For example, if you have a tablet with a connected stylus, all of the following three media queries will be applied:
@media (pointer: coarse)
— because the primary input mechanism is a finger, which has limited precision.@media (any-pointer: fine)
— because the tablet has a stylus connected, which is a precise pointer.@media (any-pointer: coarse)
— because, even though you have a stylus, the finger is still with you :)
Example usage:
/* Styles for precise pointing devices */
@media (pointer: fine) {
button {
padding: 10px;
}
}
/* Styles for devices with limited pointer precision */
@media (pointer: coarse) {
button {
padding: 20px;
}
}
In this example, buttons have different padding based on pointer precision.
hover
/ any-hover
The media features hover
and any-hover
check if the user’s input device can hover over elements. It has two possible values:
hover
: The input device supports hover (e.g., a mouse or trackpad).none
: The input device doesn’t support hover.
The difference between hover
and any-hover
is similar to the difference between pointer
and any-pointer
. hover
considers only the primary input mechanism, while any-hover
considers any input device.
For example, on a phone with a connected mouse, all of the following three media queries will be applied:
@media (hover: none)
— because the primary input device is your finger, and it does not recognize hover :)@media (any-hover: hover)
— because a mouse is connected to the phone, and it supports hover.@media (any-hover: none)
— because, even though you have a mouse, the phone still has a touchscreen.
Example usage:
/* Styles for devices that do not support hover */
@media (hover: none) {
.tooltip {
display: none;
}
}
In this example, if the device doesn’t support hover, the element with the .tooltip
class will be hidden.
update
MDN Link
Allows you to determine how often the device can update the display of content after it has been rendered.
Possible values:
none
: The device cannot update the display after rendering. Example: printed materials.slow
: The device can update the display, but at a low frequency, which is not suitable for smooth animations. Example: e-books.fast
: The device can quickly update the display, supporting smooth animations. Example: modern monitors.
Example usage:
@media (update: fast) {
.animated-element {
animation: slide 2s infinite;
}
}
@media (update: slow) {
.animated-element {
animation: none;
}
}
In this example, the animation will not be played if you view the page on, say, your e-book.
Others:
There are many more media features that might interest you, and you can find the full list on MDN. Briefly, I can also highlight:
prefers-reduced-motion
— determines whether the user prefers minimized animations.prefers-contrast
— determines whether the user prefers increased or decreased contrast.display-mode
— determines how the page is displayed (e.g., in a standalone window, fullscreen, etc.).
End
Thank you for your attention, I hope this information was helpful.
If you have any questions or comments, feel free to contact me on any of my social networks, which you can find at the bottom of the page.