Skip to main content

Media Query

  • Logical operators + media types + media features.
  • Only for improving compatibility with older browsers.
  • Definition order matters when media query with a different selector.
  • JavaScript API: window.matchMedia().
/* screen size : 500px ~ 1000px */
@media only screen and (width >= 500px) and (width <= 1000px) {
.container {
width: 750px;
}
}

Logical

Logical operators

  • only: only specific media type.
  • not: negate entire media query.
  • and: all.
  • ,: any.

CSS Media Queries Level 4:

@media (height > 600px) {
body {
line-height: 1.4;
}
}

@media (400px <= width <= 700px) {
body {
line-height: 1.4;
}
}

Type

Media types:

TypeQuery
all所有设备
print文档打印或打印预览模式
screen彩色电脑屏幕
Deprecated Query
braille盲文
embossed盲文打印
handheld手持设备
projection项目演示, 比如幻灯
speech演讲
tty固定字母间距的网格的媒体, 比如电传打字机
tv电视

Feature

Media features:

FeatureValueMin/MaxQuery
grid<integer>no是否基于格栅的设备
orientationportrait/landscapeno横屏或竖屏
aspect-ratio<integer>/<integer>yes渲染界面宽高比例
device-aspect-ratio<integer>/<integer>yes设备屏幕宽高比例
monochrome<integer>yes缓冲器中每像素字节
resolution<resolution>yes分辨率
width<length>yes渲染界面的宽度
height<length>yes渲染界面的高度
device-width<length>yes设备屏幕的输出宽度
device-height<length>yes设备屏幕的输出高度
color<integer>yes每种色彩的字节数
color-index<integer>yes色彩表中的色彩数

Print

@media print {
header,
footer {
display: none;
}

table {
color-adjust: exact;
}

h2 {
break-before: page;
}
}

Hover

hover/any-hover:

  • none.
  • hover.
<a href="#">Try hovering over me!</a>

<style>
@media (hover: hover) {
a:hover {
background: yellow;
}
}

@media (any-hover: hover) {
a:hover {
background: yellow;
}
}
</style>

Pointer

pointer/any-pointer:

  • none: no pointer device (e.g. phones).
  • coarse: limited accuracy pointer device (e.g. smart TV, video game consoles).
  • fine: accurate pointer device (e.g. mouse, touch pads, stylus).
<input id="test" type="checkbox" /> <label for="test">Look at me!</label>

<style>
input[type='checkbox'] {
margin: 0;
appearance: none;
border: solid;
}

input[type='checkbox']:checked {
background: gray;
}

@media (pointer: fine) {
input[type='checkbox'] {
width: 15px;
height: 15px;
border-color: blue;
border-width: 1px;
}
}

@media (pointer: coarse) {
input[type='checkbox'] {
width: 30px;
height: 30px;
border-color: red;
border-width: 2px;
}
}

@media (any-pointer: fine) {
input[type='checkbox'] {
width: 15px;
height: 15px;
appearance: none;
border: 1px solid blue;
}
}

@media (any-pointer: coarse) {
input[type='checkbox'] {
width: 30px;
height: 30px;
appearance: none;
border: 2px solid red;
}
}
</style>
Hover and Pointer Query Combination
Hover QueryPointer QueryDevice
nonecoarsesmartphones, touch screens
nonefinestylus-based screens
hovercoarsesmart TVs, video game consoles
hoverfinedesktop computers, laptops, touch pads

Foldable

viewport-segments:

/* stylelint-disable-next-line media-feature-name-no-unknown */
@media (horizontal-viewport-segments <= 2) and (vertical-viewport-segments <= 1) {
main article {
flex: 1 1 env(viewport-segment-width 0 0);
}

main aside {
flex: 1;
}
}

Display Mode

display-mode:

  • fullscreen.
  • standalone.
  • minimal-ui.
  • browser.
@media all and (display-mode: fullscreen) {
body {
margin: 0;
border: 5px solid black;
}
}

Resolution

<resolution>:

  • <number>dpi.
  • <number>dpcm.
  • <number>x/<number>dppx: 1dppx = 96dpi.
/* Exact resolution */
@media (resolution <= 150dpi) {
p {
color: red;
}
}

/* Minimum resolution */
@media (resolution >= 72dpi) {
p {
text-decoration: underline;
}
}

/* Maximum resolution */
@media (resolution <= 300dpi) {
p {
background: yellow;
}
}

Contrast

prefers-contrast:

  • less.
  • more.
@media (prefers-contrast: no-preference) {
html {
color: #333;
background-color: #eee;
}
}

@media (prefers-contrast: more) {
html {
color: #000;
background-color: white;
}
}

@media (prefers-contrast: less) {
html {
color: #555;
background: conic-gradient(from 90deg at 50% -10%, #bbb, 50%, #999, #bbb);
}
}

Color Scheme

prefers-color-scheme:

  • no-preference.
  • light.
  • dark.
html {
color-scheme: light dark; /* This site supports both light and dark mode */
}

:root {
/* light styles */
color-scheme: var(--color-scheme, light);

--primary-color: black;
--primary-background: white;

/* page preference is "dark" */
&:has(#color-scheme option[value='dark']:checked) {
--color-scheme: dark;

/* any additional dark styles */
--primary-color: white;
--primary-background: black;
}

/* page preference is "system", and system preference is "dark" */
@media (prefers-color-scheme: dark) {
&:has(#color-scheme option[value='system']:checked) {
--color-scheme: dark;

/* any additional dark styles, again */
--primary-color: white;
--primary-background: black;
}
}
}

body {
color: var(--primary-color);
background: var(--primary-background);
}
const ColorSchemeStorageItemName = 'preferredColorScheme'

/*
* If a color scheme preference was previously stored,
* select the corresponding option in the color scheme preference UI
* unless it is already selected.
*/
function restoreColorSchemePreference() {
const colorScheme = localStorage.getItem(ColorSchemeStorageItemName)

if (!colorScheme) {
// There is no stored preference to restore
return
}

const option = colorSchemeSelectorEl.querySelector(`[value=${colorScheme}]`)

if (!option) {
// The stored preference has no corresponding option in the UI.
localStorage.removeItem(ColorSchemeStorageItemName)
return
}

if (option.selected) {
// The stored preference's corresponding menu option is already selected
return
}

option.selected = true
}

/*
* Store an event target's value in localStorage under ColorSchemeStorageItemName
*/
function storeColorSchemePreference({ target }) {
const colorScheme = target.querySelector(':checked').value
localStorage.setItem(ColorSchemeStorageItemName, colorScheme)
}

function main() {
const colorSchemeSelectorEl = document.querySelector('#color-scheme')

if (colorSchemeSelectorEl) {
restoreColorSchemePreference()
colorSchemeSelectorEl.addEventListener('input', storeColorSchemePreference)
}
}

3 mode switch:

<select name="color-scheme-">
<option value="system">System</option>
<option value="light">Forced Light</option>
<option value="dark">Forced Dark</option>
</select>

<script>
document.querySelector('color-scheme').addEventListener('change', (e) => {
document.documentElement.setAttribute('data-force-color-mode', e.target.value)
localStorage.setItem('preferredColorScheme', e.target.value)
})

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addListener(() => {
// Make sure the dropdown is up-to-date based on mediaQuery.matches
})
</script>

<style>
:root,
:root[data-force-color-mode='light'] {
/* Default Light Mode colors + Forced Light Mode */
--primary-color: black;
--primary-background: white;
}

/* Dark Color Scheme (System Preference) */
@media (prefers-color-scheme: dark) {
:root {
--primary-color: white;
--primary-background: black;
}
}

/* Dark Color Scheme (Override) */
:root[data-force-color-mode='dark'] {
--primary-color: white;
--primary-background: black;
}
</style>

Reduced Motion

prefers-reduced-motion:

  • no-preference.
  • reduce.
@media (prefers-reduced-motion: reduce) {
.animated {
animation: none;
}
}

@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
background-attachment: initial !important;
transition-delay: 0s !important;
transition-duration: 0s !important;
animation-duration: 1ms !important;
animation-delay: -1ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
}
}

Scripting

scripting:

@media (scripting: enabled) {
.my-element {
/* enhanced styles if JS is available */
}
}

@media (scripting: none) {
.my-element {
/* fallback styles when JS is not supported */
}
}

@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
/* JS available and motion OK */
}

@media (scripting: none), (prefers-reduced-motion) {
/* JS disabled or reduced motion enabled */
}

APIs

Media query matchMedia:

// https://developer.mozilla.org/docs/Web/API/MediaQueryList
const mql = window.matchMedia(mediaQueryString)
if (window.matchMedia('(min-width: 400px)').matches) {
/* the view port is at least 400 pixels wide */
} else {
/* the view port is less than 400 pixels wide */
}

Respond to media query changes:

const match = window.matchMedia('(min-width: 400px)')

match.addEventListener('change', (e) => {
if (e.matches) {
/* do a thing */
} else {
/* do another thing */
}
})
Support Detection

Detecting media query support in CSS:

/* stylelint-disable-next-line media-feature-name-no-unknown */
@media not all and (prefers-reduced-data), (prefers-reduced-data) {
color: blue;
}
  • No support: not all and (prefers-reduced-data): false, (prefers-reduced-data): false, Combined: false.
  • Support, but off: not all and (prefers-reduced-data): true, (prefers-reduced-data): false, Combined: true.
  • Support, and on: not all and (prefers-reduced-data): false, (prefers-reduced-data): true, Combined: true.

Detecting media query support in JavaScript:

const query = '(prefers-reduced-data)'

// window.matchMedia(query).media return 'not all' or original query string
const resolvedMediaQuery = window.matchMedia(query).media

const isSupported = query === resolvedMediaQuery