Краткое изложение различных подходов к стилизации веб-страницы в соответствии с системной темой пользователя с использованием CSS, HTML и собственного javascript.

Подход 1: Просто CSS — «предпочитает цветовую схему»

Классический метод: определить предпочтение темы пользователя с помощью функции мультимедиа CSS prefers-color-scheme .

Хотя для каждой темы можно указать совершенно другой набор стилей; использование переменных CSS позволяет меньше повторяться.

Ссылка на полный код

/* file: style.css */
/* default: light theme colours */
:root {
  --bg-colour: #fafafa;  /* white */
  --font-colour: #212529;  /* dark grey*/
  --primary-colour: #2196f3;  /* blue */
  --link-colour: #2962ff;  /* blue */
  --alt-bg-colour: #fff;  /* white */
}

/* if system theme is dark */
@media(prefers-color-scheme:dark) {
  :root {
    --bg-colour: #000;  /* black */
    --font-colour: #fff;  /* white */
    --primary-colour:  #90caf9;  /* light blue */
    --link-colour: #81d4fa;  /* light blue */
    --alt-bg-colour: #5a5a5a;  /* grey */
  }
}
/* main webpage styling */
body {
  background-colour: var(--bg-colour);
  color: var(--font-color);
}
/* ... rest of styling */

Подход 2: HTML и CSS — встраивание нескольких таблиц стилей

Атрибут media позволяет нам указать тип носителя (в данном случае, светлая/темная системная тема), для которого предназначен целевой ресурс (файл таблицы стилей).

Ссылка на полный код

<!-- file: index.html -->
<head>
  <!-- other elements in head-->
  <!-- stylesheet with dark theme css variables -->
  <link rel="stylesheet" media="(prefers-color-scheme:dark)" href="dark.css">
  
  <!-- stylesheet with light theme css variables -->
  <link rel="stylesheet" media="(prefers-color-scheme:light)" href="light.css">
  <!-- base stylesheet -->
  <link rel="stylesheet" href="style.css">
</head>
<!-- rest of html -->
/* file: light.css */
:root {
  --bg-colour: #fafafa;  /* white */
  --font-colour: #212529;  /* dark grey*/
  --primary-colour: #2196f3;  /* blue */
  --link-colour: #2962ff;  /* blue */
  --alt-bg-colour: #fff;  /* white */
}
/* file: dark.css */
:root {
  --bg-colour: #000;  /* black */
  --font-colour: #fff;  /* white */
  --primary-colour:  #90caf9;  /* light blue */
  --link-colour: #81d4fa;  /* light blue */
  --alt-bg-colour: #5a5a5a;  /* grey */
}
/* file: style.css */
body {
  background-colour: var(--bg-colour);
  color: var(--font-color);
}
/* ... rest of styling */

Атрибут media определяет, какую таблицу стилей заполнить переменными CSS, которые будут использоваться в style.css для стилизации веб-страницы. Для более подробного ознакомления с тем, как работает этот метод, и стратегией загрузки, У Томаса Штайнера есть хорошо написанная статья с более подробной информацией об этом методе.

Подход 3: JS — Изменение стиля/таблицы стилей

3.1 Изменение переменных CSS

Этот подход изменяет переменные CSS, чтобы они соответствовали системной теме пользователя, используя собственный javascript.

Ссылка на полный код

<!-- file: index.html -->
<head>
  <!-- other elements in head-->
  
  <!-- base stylesheet -->
  <link rel="stylesheet" href="style.css">
  <!-- script -->
  <script src="script.js"></script>
</head>
<!-- rest of html -->
/* file: style.css */
/* set light theme colours as default */
:root {
  --bg-colour: #fafafa;
  --font-colour: #212529;
  --primary-colour:  #2196f3;
  --link-colour: #2962ff;
  --alt-contrast-colour: #fff;
}
/* ... rest of styling */

Чтобы определить, использует ли система пользователя темную тему в javascript, метод заключается в использовании window.matchMedia("(prefers-color-scheme:dark)").matches, который проверяет, соответствует ли document (веб-страница) строке медиа-запроса (prefers-color-scheme:dark — является ли системная тема пользователя темной).

// file: script.js
// light theme colours
const lightTheme = {
  "--bg-colour": "#fafafa",
  "--font-colour": "#212529",
  "--primary-colour": "#2196f3",
  "--link-colour": "#2962ff",
  "--alt-contrast-colour": "#fff"
};
// dark theme colours
const darkTheme = {
  "--bg-colour": "#000",
  "--font-colour": "#fff",
  "--primary-colour":  "#90caf9",
  "--link-colour": "#81d4fa",
  "--alt-contrast-colour": "#5a5a5a"
};
// root element to be used later to change CSS variables' values
let root = document.documentElement;
/**
* Changes the webpage's colours based on colours object passed in
*
* @param {object} themeColours object with the CSS variables and their corresponding colour values
*/
function setTheme(themeColours) {
  console.log("setting theme: " + themeColours);
  for (var name in themeColours) {
    root.style.setProperty(name, themeColours[name]);
  };
};
// waits for DOM to finish loading
document.addEventListener("DOMContentLoaded", function(event) {
  // initial determination of user's system theme
  var isDarkTheme = (window.matchMedia("(prefers-color-scheme: dark)"));
  (isDarkTheme.matches)?setTheme(darkTheme):setTheme(lightTheme);
  // when user changes system theme, also change webpage colours
  isDarkTheme.addEventListener('change', (event) => {
    (isDarkTheme.matches)?setTheme(darkTheme):setTheme(lightTheme);
  });
});

3.2 Изменение пути к таблице стилей

Таблицы стилей для этого подхода (light.css, dark.css, style.css) такие же, как уже упоминалось для подхода 2.

Ссылка на полный код

<!-- file: index.html -->
<head>
  <!-- other elements in head-->
  
  <!-- default: light theme css variables -->
  <link rel="stylesheet" href="light.css" id="theme-colours">
  <!-- base stylesheet -->
  <link rel="stylesheet" href="style.css">
  <!-- script -->
  <script src="script.js"></script>
</head>
<!-- rest of html -->
// file: script.js
// theme colours
const lightThemeStylesheet = "light.css";
const darkThemeStylesheet = "dark.css";
// gets element for the stylesheet that sets theme colours
var themeLink = document.getElementById("theme-colours");
// waits for DOM to finish loading
document.addEventListener("DOMContentLoaded", function(event) {
  // initial determination and setting of user's system theme
  var isDarkTheme = (window.matchMedia("(prefers-color-scheme: dark)"));
  // sets themeLink
  themeLink.href = isDarkTheme.matches ? darkThemeStylesheet : lightThemeStylesheet;
// when user changes system theme, also change webpage's stylesheet
  isDarkTheme.addEventListener('change', (event) => {
      themeLink.href = isDarkTheme.matches ? darkThemeStylesheet : lightThemeStylesheet;
  });
});

3.3 Изменение атрибута данных `data-theme`

Этот подход использует атрибуты данных, которые позволяют нам хранить дополнительную информацию в HTML-коде веб-страницы. В этом случае javascript обнаруживает и сохраняет системную тему пользователя в data-theme.

Ссылка на полный код

<!-- file: index.html -->
<html lang="en">
<head>
  <!-- other elements in head-->
  
  <!-- base stylesheet -->
  <link rel="stylesheet" href="style.css">
<!-- script -->
  <script src="script.js"></script>
</head>
<!-- rest of html -->
</html>

В index.html у <html> нет атрибута data-theme, это не ошибка — по умолчанию для переменных CSS root установлена ​​светлая тема в style.css (так что есть стиль по умолчанию, к которому можно вернуться), а атрибут установлен в script.js .

/* style.css */
/* default: light theme colours */
:root {
  --bg-colour: #fafafa;
  --font-colour: #212529;
  --primary-colour:  #2196f3;
  --link-colour: #2962ff;
  --alt-contrast-colour: #fff;
}
/* dark theme colours */
html[data-theme="dark"] {
  --bg-colour: #000;
  --font-colour: #fff;
  --primary-colour:  #90caf9;
  --link-colour: #81d4fa;
  --alt-contrast-colour: #5a5a5a;
}
/* ... rest of styling */
// style.js
// get html element
const htmlEl = document.querySelector("html");
// waits for DOM to finish loading
document.addEventListener("DOMContentLoaded", function(event) {
  // initial determination and setting of user's system theme (data-theme)
  var isDarkTheme = (window.matchMedia("(prefers-color-scheme: dark)"));
  htmlEl.dataset.theme = isDarkTheme.matches ? "dark" : "light";
  
  // when user changes system theme, also change webpage's stylesheet
  isDarkTheme.addEventListener('change', (event) => {
  htmlEl.dataset.theme = isDarkTheme.matches ? "dark" : "light";
  });
});

Дополнительно: разрешение пользователям переопределять тему системы по умолчанию

Предоставление пользователям выбора! Также рекомендуется сохранить выбранную пользователем предпочтительную тему (с localstorage), чтобы в следующий раз, когда он вернется, ему не нужно было снова устанавливать предпочитаемую тему.

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

Также были добавлены некоторые шаги, чтобы изменить цвет панели навигации, изменив ее класс начальной загрузки.

Ссылка на полный код: изменен подход 3.2 (тот же HTML) с дополнительным CSS для переключателя темы.

// file: script.js
// waits for DOM to finish loading
document.addEventListener("DOMContentLoaded",   function(event) {
  // theme colours
  const lightThemeStylesheet = "light.css";
  const darkThemeStylesheet = "dark.css";
  
  // gets element for the stylesheet that sets theme colours
  var themeLink = document.getElementById("theme-colours");
  // element for theme switch toggle
  var themeSwitch = document.getElementById("theme-switch");
  // element for navbar
  var navbar = document.getElementById("navbar");
  /**
  * Changes the webpage's colours, theme switch and navbar colour
  *
  * @param {string} "light" or "dark"
  */
  function setTheme(theme) {
    if (theme=="dark") {
      localStorage.setItem("theme", "dark");
      themeLink.href = darkThemeStylesheet;
      themeSwitch.checked = true;
      // removes bootstrap light navbar classes and add the dark navbar classes
      navbar.classList.remove("bg-light", "navbar-light");
      navbar.classList.add("bg-dark", "navbar-dark");
    } else {
      localStorage.setItem("theme", "light");
      themeLink.href = lightThemeStylesheet;
      themeSwitch.checked = false;
      // removes bootstrap dark navbar classes and add the light navbar classes
      navbar.classList.remove("bg-dark", "navbar-dark");
      navbar.classList.add("bg-light", "navbar-light");
    }
  };
// get element of the theme switch toggle
var themeSwitch = document.getElementById("theme-switch");
var selectedTheme = localStorage.getItem("theme");
  if (!selectedTheme) {
    // if no theme in local storage, set to system default
    (window.matchMedia("(prefers-color-scheme: dark)")).matches ? setTheme("dark") : setTheme("light");
  } else {
    setTheme(selectedTheme);
  }
// when user toggles theme switch, change the theme
  themeSwitch.addEventListener('change', (event) => {
    (themeSwitch.checked) ? setTheme("dark") : setTheme("light");
  });
});

Этот подход можно расширить, чтобы использовать раскрывающийся список/список параметров и предоставить пользователям больше вариантов выбора тем (высокая контрастность и т. д.) — например. добавление таблицы стилей high-contrast.css и установка ее в качестве выбранной таблицы стилей.

Дополнительные соображения

  • Убедитесь, что цвета достаточно контрастны, чтобы текст и интерактивные компоненты, такие как кнопки, выделялись на фоне и оставались видимыми.
  • Было бы неплохо также сопоставить изображения с темой. Например. использование более темных изображений, когда тема темная, использование темного фона на изображениях графиков
  • Для брендинга рассмотрите возможность использования логотипов, которые либо работают на обе темы, либо имеют вариации, которые можно настроить для разных тем.