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
only: only specific media type.not: negate entire media query.and: all.,: any.
@media (height > 600px) {
body {
line-height: 1.4;
}
}
@media (400px <= width <= 700px) {
body {
line-height: 1.4;
}
}
Type
| Type | Query |
|---|---|
| all | 所有设备 |
| 文档打印或打印预览模式 | |
| screen | 彩色电脑屏幕 |
| Deprecated Query | |
| braille | 盲文 |
| embossed | 盲文打印 |
| handheld | 手持设备 |
| projection | 项目演示, 比如幻灯 |
| speech | 演讲 |
| tty | 固定字母间距的网格的媒体, 比如电传打字机 |
| tv | 电视 |
Feature
| Feature | Value | Min/Max | Query |
|---|---|---|---|
| grid | <integer> | no | 是否基于格栅的设备 |
| orientation | portrait/landscape | no | 横屏或竖屏 |
| 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;
}
}
- Page style standard specification.
- PDF style tutorial.
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 Query | Pointer Query | Device |
|---|---|---|
| none | coarse | smartphones, touch screens |
| none | fine | stylus-based screens |
| hover | coarse | smart TVs, video game consoles |
| hover | fine | desktop computers, laptops, touch pads |
Foldable
/* 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
fullscreen.standalone.minimal-ui.browser.
@media all and (display-mode: fullscreen) {
body {
margin: 0;
border: 5px solid black;
}
}
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
@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