
The HDR Threshold
Why your high-end monitor is showing you washed-out colors and how to fix it using the CSS dynamic-range media query.
Your $1,200 OLED monitor is lying to you. You’ve enabled HDR in Windows or macOS, your peak brightness is set to 1,000 nits, and yet, when you open a browser, the colors look flat, greyish, and strangely "thin." This is the HDR threshold—the invisible wall between the legacy 8-bit sRGB world we built the web on and the high-luminance, wide-gamut reality of modern hardware.
When a display operates in High Dynamic Range (HDR) mode, it doesn't just make everything brighter. It expands the "headroom"—the space between the darkest black and the brightest white. Standard web content is designed for Standard Dynamic Range (SDR), which targets a peak brightness of about 80 to 100 nits. When your OS maps that 100-nit SDR content onto a 1,000-nit HDR canvas, it often feels "washed out" because the software is trying to preserve the creator's intent by not blowing out the colors, leaving the rest of your monitor's power untapped.
We can fix this. CSS now gives us the tools to detect this headroom and push past the SDR ceiling.
The Problem with the 0-255 Mental Model
For thirty years, web design has lived in a box called sRGB. We’ve been trained to think that #ffffff is the "brightest" white possible. But in the world of HDR, #ffffff is just the start of the "Standard" range. Beyond that lies the specular highlights—the glint of sun on water, the glow of a lightsaber, or the vibrant neon of a cyberpunk UI.
If you keep using standard sRGB hex codes on an HDR display, you’re essentially driving a Ferrari in a school zone. You’re capped. To break out, we need to understand the dynamic-range media query.
Detecting the Headroom
The dynamic-range media feature allows us to query the capabilities of the output device. It’s not just about whether the screen *supports* HDR, but whether it is currently *rendering* in a high dynamic range state.
/* The standard approach */
@media (dynamic-range: high) {
/* High-fidelity styles go here */
:root {
--accent-glow: oklch(70% 0.3 260);
--hero-brightness: 1.5;
}
}There are two values for this query: standard and high.
- `standard`: The display supports a limited range of contrast and color (the traditional web).
- `high`: The display supports high peak brightness, high contrast ratios, and usually a wider color gamut (like P3 or Rec. 2020).
Why Colors Look "Washed Out"
When you enable HDR on a monitor, the OS takes over the color management. Because the monitor is capable of much higher brightness than standard content, the OS often compresses the SDR range to ensure that a white background on a blog post doesn't blind you. This is called "SDR White Level" mapping.
The result is that your "white" isn't actually white; it's a dull grey compared to what the monitor can actually do. If you want a specific element—like a call-to-action button or a hero image—to actually pop, you have to explicitly tell the browser to use the extended range.
Beyond Hex: P3 and OKLCH
To truly utilize the HDR threshold, you have to stop using Hex, RGB, and HSL. These color spaces are mathematically locked into the sRGB gamut. Even if you wrap a Hex code in a dynamic-range: high media query, it won't look any different because #ff0000 in sRGB is still just the most "red" red that a 1990s monitor could produce.
To access the "super-colors," we use display-p3 or the newer oklch().
/* Standard Red */
.button {
background-color: rgb(255, 0, 0);
}
/* HDR-Ready Red */
@media (dynamic-range: high) {
.button {
/* OKLCH allows us to push chroma (the second value)
beyond the sRGB limit into the P3 space */
background-color: oklch(60% 0.35 20);
}
}In the example above, oklch(60% 0.35 20) represents a red that is physically impossible to display on a standard sRGB monitor. On an HDR screen, it will look incredibly vivid—almost like it’s glowing—compared to the "standard" red.
Practical Implementation: The "Glow" Effect
Let’s look at a practical scenario. Imagine a "Live" indicator for a streaming site. On a standard screen, it should be a bright red. On an HDR screen, we want it to actually look like a glowing LED.
.live-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #ff0000;
box-shadow: 0 0 5px rgba(255, 0, 0, 0.5);
}
@media (dynamic-range: high) {
.live-indicator {
/* We use a P3 color for the background */
background-color: color(display-p3 1 0 0);
/* We use a high-chroma OKLCH for the glow,
making it appear more luminous */
box-shadow: 0 0 15px oklch(65% 0.4 20 / 0.8);
/* We can also use relative color syntax to bump
the lightness of the existing color */
outline: 2px solid oklch(from color(display-p3 1 0 0) calc(l + 0.2) c h);
}
}The "Video" Problem and video-dynamic-range
Sometimes, the washed-out look isn't about UI elements, but about video. HDR video (HDR10, Dolby Vision) is increasingly common. However, browsers handle HDR video differently depending on the hardware.
You can use the video-dynamic-range media query to serve different assets or UI overlays based on whether the hardware can handle HDR video playback.
@media (video-dynamic-range: high) {
.video-container::after {
content: "4K HDR";
padding: 4px 8px;
background: rgba(0, 0, 0, 0.5);
color: white;
border: 1px solid oklch(80% 0.2 150); /* A subtle HDR-only border glow */
}
}The "Gotcha": Color Fatigue and Battery Life
There is a temptation to "HDR-ify" everything once you realize you have the power. Don't.
HDR consumes significantly more power on mobile devices and laptops. Pushing pixels to 1,000 nits drains the battery faster than standard rendering. Furthermore, "retinal fatigue" is real. If you make your body text "HDR White," your users will get a headache within ten minutes.
The Rule of Thumb: Use HDR for "speculars."
- Highlights on buttons
- Glows and shadows
- Accents in illustrations
- Data visualization peaks
- Photography/Video
Keep your primary text and background within the standard luminance range. It creates a better hierarchy. If everything is bright, nothing is bright.
Combining Queries for the Ultimate Fallback
Not all HDR screens are created equal. Some are "FakeHDR" (HDR400) which barely have more headroom than a standard screen. Others are OLED panels with perfect blacks. While we can't query "Max Nits" yet, we can combine dynamic-range with prefers-color-scheme to create sophisticated themes.
:root {
--primary-bg: #ffffff;
--text: #1a1a1a;
--accent: #0070f3;
}
@media (prefers-color-scheme: dark) {
:root {
--primary-bg: #0a0a0a;
--text: #ededed;
--accent: #3291ff;
}
}
/* Enhancing the Dark Theme for HDR users */
@media (prefers-color-scheme: dark) and (dynamic-range: high) {
:root {
/* On an HDR screen (especially OLED), we can go
"True Black" without losing the UI in the glare */
--primary-bg: #000000;
/* We can make the text slightly dimmer to reduce
eye strain against the pure black background */
--text: oklch(85% 0 0);
/* But we make the accent punch through the screen */
--accent: oklch(70% 0.3 250);
}
}The Role of color-gamut
It is easy to confuse dynamic-range with color-gamut.
- dynamic-range refers to luminance and contrast (the "brightness" range).
- color-gamut refers to the range of colors (the "vividness" range).
A screen can support a wide color gamut (P3) but have poor dynamic range (low peak brightness). Or it can be HDR but have a limited gamut (though this is rare). For the best results, you should check both if you are serving high-end photographic content.
@media (color-gamut: p3) and (dynamic-range: high) {
/* This is the sweet spot for premium displays like
the MacBook Pro or high-end iPhones */
.hero-image {
background-image: url('hero-hdr-p3.avif');
}
}Why AVIF is your best friend
If you're still using JPEGs for your big hero images, you're missing the HDR boat. JPEG is an 8-bit format. It literally cannot store HDR data.
To show HDR images on the web, you need a format that supports 10-bit or 12-bit color depth. AVIF is currently the king of this space. It supports HDR metadata and is widely supported in modern browsers. When you save an HDR image as an AVIF, the browser can interpret that extra luminance data and display it correctly on an HDR monitor while gracefully downscaling for SDR users.
<picture>
<!-- HDR version for capable browsers/screens -->
<source srcset="scenery-hdr.avif" type="image/avif" media="(dynamic-range: high)">
<!-- Standard version -->
<img src="scenery-sdr.jpg" alt="A beautiful mountain view">
</picture>Designing for the Threshold
Designing for HDR requires a shift in mindset. We are moving away from "fixed" colors toward "fluid" intent.
When I’m building a site now, I don't just think about "Blue." I think about "The Blue that this specific user's screen is capable of showing." By using dynamic-range and wide-gamut color spaces like oklch(), we can ensure that our sites don't look washed out on $2,000 setups, but still look perfectly legible on a five-year-old Chromebook.
The "washed out" look is a symptom of the web's transition phase. We are currently in the middle of a "Great Brightening." The browsers have given us the bridge; it's up to us to start walking across it.
Testing without the Gear
One final tip: How do you test this if you're on a standard office monitor?
In Chrome DevTools, you can emulate these media features.
1. Open DevTools (F12).
2. Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows).
3. Type "Rendering" and select "Show Rendering."
4. Scroll down to "Emulate CSS media feature dynamic-range."
5. Toggle it to high.
While this won't actually make your monitor brighter, it will trigger your @media queries, allowing you to debug your logic and ensure your P3 colors are being applied correctly.
The HDR threshold is here. Stop letting your beautiful designs get flattened by 1990s color standards. Pushing those extra few hundred nits might just be the "wow factor" your next project is missing.


