```符合网络标准的深色模式```
Dark mode with web standards

原始链接: https://olliewilliams.xyz/blog/dark-mode/

为了有效地支持深色和浅色模式,网站应默认遵循用户的操作系统偏好,并同时提供手动覆盖的选项。最稳健的方法是在文档的 `` 中使用 `` 标签,这能确保快速渲染,并允许 JavaScript 通过更新该元标签的 content 属性来动态切换模式。 虽然 `color-scheme` 可以控制系统颜色、滚动条和 `light-dark()` CSS 函数,但它与 `prefers-color-scheme` 媒体查询是不同的。这带来了一个挑战:使用 `color-scheme` 进行页面内切换时,无法触发基于媒体查询的资源,如 `` 元素。 为了解决这个问题,开发人员可以使用 CSS 样式查询或 `@property` 技巧来响应当前生效的配色方案。通过定义随文档 `color-scheme` 变化的自定义属性,你可以在标准媒体查询失效时应用条件样式——例如将阴影替换为边框。尽管未来的规范可能允许 JavaScript 直接覆盖 `prefers-color-scheme`,但目前,将 `color-scheme` 与 CSS 变量同步结合使用,仍是实现自定义站点主题控制最可靠的方法。

Hacker News | 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 基于 Web 标准的深色模式 (olliewilliams.xyz) 11 分,由 thm 发布于 5 小时前 | 隐藏 | 过往 | 收藏 | 讨论 | 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Respecting the user’s OS setting is straightforward: use the prefers-color-scheme media query in CSS. Arguably, that isn’t enough: users should also be able to customise their choice on a per-site basis. A user might want dark mode for the UI of an application, but light mode for reading long-form text on content-heavy sites, for example.

We need to:

  1. Support the user’s system setting as the default for when the user lands on our website for the first time
  2. Allow the user to override their system setting with a toggle in our application.

The color scheme of a web page can be set either via a HTML meta tag in the <head> of the document or via the CSS color-scheme property on the html element. It can take time for CSS to load on slow connections, so using the meta tag is the recommended approach. When a user lands on your website for the first time, respect their system preference by setting <meta name="color-scheme" content="light dark">. To override the OS setting via a control in your web app or site, use JavaScript to update the content attribute value to light to force light mode, dark to force dark mode, or light dark to revert back to the OS setting.

const metaTag = document.querySelector('[name="color-scheme"]');

const savedScheme = localStorage.getItem("colorScheme");
if (savedScheme) {metaTag.setAttribute('content', savedScheme);}

btnlight.addEventListener('click', function() {
    metaTag.setAttribute('content', 'light');
    localStorage.setItem("colorScheme", "light");
});
btndark.addEventListener('click', function() {
    metaTag.setAttribute('content', 'dark');
    localStorage.setItem("colorScheme", "dark");
});
btnsystem.addEventListener('click', function() {
    metaTag.setAttribute('content', 'light dark');
    localStorage.removeItem("colorScheme");
});

What does the color-scheme affect?

  • Colors, gradients, or images set via the light-dark() CSS function
  • System colors like Canvas and CanvasText
  • Scrollbar colors
  • The default colors of HTML elements like buttons
  • iframes styles (so long as the iframe document has opted in via the meta tag)
  • SVG’s that make use of light-dark() or prefers-color-scheme

What doesn’t color-scheme affect?

There’s an unfortunate disconnect between color-scheme and the prefers-color-scheme media query. prefers-color-scheme reflects the OS settings — regardless of the color-scheme value. If you’re providing an in-page toggle that implements dark mode, you can’t adopt the prefers-color-scheme media query.

The following code is not be impacted by color-scheme:

<picture>
  <source srcset="logo-dark.png" media="(prefers-color-scheme: dark)" />
  <img src="logo-light.png" alt="Product logo" />
</picture>

Other than making use of a background-image, there’s sadly not an equivalent approach to the <picture> element that references the color-scheme.

There are two exceptions where color-scheme will affect the prefers-color-scheme media query:

Below are two iframes that show the same document. The document inside the iframe is making use of the prefers-color-scheme media query.

<iframe style="color-scheme: light;" src="/example.html"></iframe>
<iframe style="color-scheme: dark;" src="/example.html"></iframe>

As you can see in the above example, the @media (prefers-color-scheme: dark) styles are being applied when color-scheme: dark is used in the parent document.

The same principle applies to SVG. Here’s the content of an .svg file:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<style>
circle {
    fill: rgb(40,40,40);
}

@media (prefers-color-scheme: dark) {
    circle {
        fill: rgb(216, 216, 216);
    }
}
</style>
  <circle cx="50" cy="50" r="50" />
</svg>
<img style="color-scheme: light;" src="/circle.svg" alt="">
<img style="color-scheme: dark;" src="/circle.svg" alt="">

The CSS spec has recently been updated so that the color-scheme of the document does effect the media query in all contexts, but no browser has implemented this.

Some Safari caveats

  • Support for the prefers-color-scheme media query within SVG was added in Safari 27, but color-scheme does not affect the media query (see bug report)
  • Support for the prefers-color-scheme media query within iframes was added in Safari 27 and the color-scheme of the parent document does override it, as it should 🎉. However, other bugs remain (see bug report)

Using light-dark() with images and gradients

The light-dark() function was originally limited to colors. It can now be used for gradients and images (as of Chrome/Edge version 150, Firefox version 150, and Safari Technology Preview).

.bg-gradient {
  background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), linear-gradient(15deg, #6b7495, #001339)); 
}
<div class="bg-gradient" style="color-scheme: light;"></div>
<div class="bg-gradient" style="color-scheme: dark;"></div>

It’s also possible to switch between a single solid color and a gradient, depending on the color-scheme.

.bg-grad-solid {
    background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), image(#001339)); 
}

Images

.bg {
    background-image: light-dark(url(/lightmode.avif), url(/darkmode.avif));
}

Changing more than colors, images and gradients

By and large, the thing you need to change between modes is color, but there are exceptions. A box-shadow might not be visible on a dark background in dark mode, so you may decide to apply a border instead, for example. Implementing that is currently rather challenging. The CSS standards body is planning to add a way to detect the current color-scheme using either a CSS if() statement or a style query, but no browser has implemented this feature. It’s possible to hack together an alternative. Below I’ll look at two approaches.

Define a CSS variable to be true when the page is using dark mode:

html {
    --dark: false;
} 

html:has([content="light dark"]) {
 @media (prefers-color-scheme: dark) {
    --dark: true;
 }
}

html:has([content="dark"]) {
    --dark: true;
}

All browsers now support style queries, which allow you to apply styles depending on the value of a custom property:

@container style(--dark: false) {
.card {
    box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
}

@container style(--dark: true) {
  .card {
    border: solid 1px rgb(94, 94, 94);
  }
}

A better approach that also uses style queries would be:

@property --usedScheme {
        syntax: "<color>";
        inherits: true;
        initial-value: transparent;
    }

body {
    --usedScheme: light-dark(white, black);
}

@container style(--usedScheme: white) {
    .card {
        box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
    }
}


@container style(--usedScheme: black) {
  .card {
    border: solid 1px rgb(94, 94, 94);
  }
}

A (possible) future: overriding prefers-color-scheme via JS

We might get a way to override the prefers-color-scheme media query with JavaScript in the future. There’s a spec, an MDN entry and a prototype in Chrome Canary but the Safari team are opposed to the idea.

联系我们 contact @ memedata.com