Светлая и тёмная темы для отдельных компонентов

Веб-функции: color-scheme light-dark()

Хотя свойство color-scheme чаще всего задается в корневом элементе, его можно задать и для отдельных элементов, чтобы применить к ним цветовую схему, отличную от остальной части страницы. Это может быть полезно для компонентов, которые всегда должны отображаться в определенной цветовой схеме (например, всегда в темном или светлом режиме).

Примеры использования:

  • Элементы, которые часто отображаются в темном режиме даже на страницах со светлой темой по эстетическим причинам, например блоки кода, медиапроигрыватели, фотогалереи.
  • Области, содержащие медиаконтент, предназначенный для светлого фона (например, изображения, видео, иллюстрации, предварительный просмотр печати), можно настроить на светлый режим, даже если остальная часть страницы находится в тёмном режиме.
  • Элементы, цветовая схема которых контролируется настройкой на уровне пользователя, например предварительный просмотр компонентов
  • Встроенные объекты, которые не поддерживают светлую и тёмную темы.
  • Инструменты дизайна, карты, визуализации, игры и т. д.

Когда нужно менять цвета, а когда – принудительно использовать color-scheme?

Не для каждого элемента, в котором используется более светлый текст на более темном фоне в светлом режиме или более темный текст на более светлом фоне в темном режиме, требуется другой color-scheme. Например, основная кнопка может быть синей с белым текстом в светлом режиме, но это не гарантирует color-scheme: dark.

Как правило, элементы, использующие другой color-scheme, представляют собой сложные поверхности, создающие свой собственный визуальный контекст, а не простые неглубокие контейнеры.

Если вы хотите использовать для элемента другой атрибут color-scheme, задайте себе следующие вопросы:

  • Должен ли встроенный интерфейс браузера, который не был настроен иначе (например, элементы управления формами, полосы прокрутки и т.д.), использовать эту цветовую схему или адаптироваться к цветовой схеме страницы? -> if the former, don't use color-scheme.
  • Должны ли цвета light-dark() определяться так же, как для остальной части страницы, или на основе переопределения? -> if the former, don't use color-scheme.
  • Должны ли потомки быть в этом color-scheme? В противном случае не используйте color-scheme.

Базовый вариант

Переопределения для отдельных компонентов обычно (но не обязательно) используются на страницах, которые также поддерживают несколько цветовых схем через глобальный color-scheme. Подробнее о том, как правильно реализовать тёмную тему на всей странице…

Если на странице есть color-scheme и вы используете токены цвета, которые зависят от него (например, через light-dark()), вы можете просто задать color-scheme для определенных компонентов, чтобы переопределить цветовой режим для этого поддерева:

pre, code, .dark {
  color-scheme: dark;
}

Обратите внимание, что некоторые браузеры автоматически адаптируют компоненты к другой цветовой схеме. Чтобы принудительно использовать указанную цветовую схему во всех случаях, используйте only, то есть color-scheme: only dark; вместо color-scheme: dark;.

Адаптация значений, не связанных с цветом

Сейчас light-dark() работает только для цветов.

Рекомендации

  • ОБЯЗАТЕЛЬНО. Не задавайте color-scheme для элементов без фона, так как это может привести к смешиванию цветов фона и текста из разных цветовых схем, в результате чего текст станет нечитаемым.
  • НЕОБЯЗАТЕЛЬНО. Проще всего использовать те же пары цветов, что и для темной темы на уровне страницы, но мы можем задать для этих компонентов другие пары цветов. Например, компонент тёмной темы, используемый на странице со светлой темой, должен быть немного менее тёмным, чем тот же компонент на странице с тёмной темой.

Известные проблемы

Важное замечание: наследование цветов light-dark()

light-dark() разрешается во время вычисления значения. Это означает, что любые унаследованные свойства <color>, для которых задан цвет light-dark(), передадут своим дочерним элементам только один из двух цветов, а не само выражение light-dark().

Примеры:

  • Встроенные наследуемые свойства цвета, такие как color, accent-color, fill, stroke, text-shadow, caret-color.
  • Любые зарегистрированные наследуемые дополнительные параметры с syntax: <color> и inherits: true.
  • Любое другое свойство <color> со значением inherit

Это означает, что вам нужно:

  • НЕ регистрируйте специальные свойства, предназначенные для хранения дизайн-токенов (например, --surface-color), как <color>. Токены должны сохранять свое выражение light-dark(), чтобы дочерние элементы могли повторно разрешать их в другом color-scheme.
  • При настройке color-scheme для элемента повторно укажите все унаследованные свойства <color>, для которых могли быть заданы значения light-dark() (напрямую или через токены дизайна), даже если это тот же токен дизайна.
  • НЕ используйте inherit для свойств <color> в элементах с переопределением color-scheme (можно использовать для их дочерних элементов).
  • ИСПОЛЬЗУЙТЕ зарегистрированные свойства <color> для противоположного случая: когда вы намеренно хотите сделать снимок разрешенного цвета предка и предотвратить его повторное разрешение в color-scheme потомка. Например, вы можете сохранить фон страницы, чтобы использовать его в другом месте.
  • Если вам нужно анимировать цвет, используйте отдельное свойство <color>, зарегистрированное в @property, для анимируемого элемента (регистрация требуется для интерполяции цвета). Это не токен дизайна, а цель анимации для каждого элемента, поэтому он не конфликтует с приведенным выше правилом.

Пример:

:root {
    --accent-color: light-dark(blue, skyblue);
    --surface-color: light-dark(white, #222);
    --text-color: light-dark(#111, white);

    color-scheme: light dark;
    accent-color: var(--accent-color);
    color: var(--text-color);
}

body {
    /* --surface-color dynamically switches despite being inherited because --surface-color is not registered */
    background: var(--surface-color);
}

pre, code {
    color-scheme: dark;
    background: var(--surface-color);

    /* Without this, accent-color would be blue, not skyblue! */
    accent-color: var(--accent-color);

    /* Without this, text-color would be #111, not white! */
    color: var(--text-color);
}

Проблемы, о которых следует знать при использовании цветовой схемы

  • Chrome и Firefox поддерживают color-scheme для элементов iframe: они отображают встроенные страницы в правильной цветовой схеме и корректируют запрос медиа prefers-color-scheme встроенной страницы, чтобы отразить color-scheme контекста встраивания. Safari этого не делает и разрешает prefers-color-scheme в системных настройках даже внутри iframe.
    • Если вы управляете и родительским элементом, и элементом iframe, передайте цветовую схему родительского элемента в iframe явным образом – с помощью параметра URL (?theme=dark) во время создания iframe или с помощью postMessage() (что также позволяет реагировать на изменения во время выполнения). В iframe задайте класс для <html> (и/или color-scheme для :root) на основе этого сигнала, а не prefers-color-scheme.
    • Если вы управляете только встроенной страницей, то в Safari нет надежного способа определить color-scheme контекста встраивания из iframe. Добавьте в API для встраивания явный параметр темы (например, строку запроса или протокол postMessage) и опишите его в документации для разработчиков.

Резервные стратегии

Резервные варианты и поддержка свойства color-scheme в браузерах

Свойство color-scheme – это прогрессивное улучшение. Браузеры, которые не поддерживают это свойство, будут игнорировать его и использовать светлую тему по умолчанию.

Чтобы адаптировать сайт к предпочтениям пользователя в старых браузерах, используйте медиазапросы prefers-color-scheme, чтобы задать другие цвета для тёмной темы.

  • Используйте запрос медиа, чтобы переключать дополнительные параметры в :root или html.
  • Не используйте запрос медиа для отдельных компонентов, если только для них не требуется особый тип настройки темной темы, не связанный с цветами.
:root {
  /* Define brand colors for each mode */
  --color-brand-light: #0056b3;
  --color-brand-dark: #00e5ff;
  --color-brand: var(--color-brand-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --color-brand: var(--color-brand-dark);
  }

  /* Ignored in older browsers */
  color-scheme: light dark;
}

button.primary {
    background-color: var(--color-brand);
}

Резервные варианты и поддержка light-dark() в браузерах

Для браузеров, которые поддерживают color-scheme, но пока не поддерживают light-dark(), светлые и темные версии цветов сначала должны быть определены как настраиваемые свойства, а запрос медиа prefers-color-scheme должен использоваться для установки цветов для соответствующего режима, как в примере ниже:

:root {
  /* Define browser UI accent color for each mode */
  --brand-accent-light: #0056b3;
  --brand-accent-dark: #00e5ff;
  --accent-color: var(--brand-accent-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --accent-color: var(--brand-accent-dark);
  }

  /* OPTIONAL: use light-dark() for more control of built-in UI colors */
  @supports (color: light-dark(white, black)) {
    --accent-color: light-dark(var(--brand-accent-light), var(--brand-accent-dark));
  }

  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;

  /* Example inherited color property */
  accent-color: var(--accent-color);
}

pre, code {
  color-scheme: dark;

  /* **Mandatory**: any inherited color properties must be set again, even if to the same design tokens */
  accent-color: var(--accent-color);
}