From b0c588061bf890eb773dc304925977ebce4f5b4b Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Tue, 11 Nov 2025 09:22:28 +0000 Subject: [PATCH 1/3] =?UTF-8?q?Add=20dark=20mode=20support=20for=20built-i?= =?UTF-8?q?n=20themes=20=E2=80=93=20fixes=20#13742?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds automatic dark mode support to Sphinx's built-in themes (basic, classic, haiku, nature, and sphinxdoc) using CSS prefers-color-scheme media queries. The dark mode activates automatically based on the user's system/browser theme preference. Additionally, an optional JavaScript toggle (theme_toggle.js) is provided in the basic theme to allow users to manually switch between light and dark modes, with the preference saved in localStorage. Features: - Dark mode CSS for basic, classic, haiku, nature, and sphinxdoc themes - Automatic activation via prefers-color-scheme media query - Optional manual toggle with theme_toggle.js - Improved contrast and readability in dark mode - Support for both automatic and manual theme switching --- CHANGES.rst | 6 + sphinx/themes/basic/static/basic.css.jinja | 263 ++++++++++++++++++ sphinx/themes/basic/static/theme_toggle.js | 126 +++++++++ .../themes/classic/static/classic.css.jinja | 162 +++++++++++ sphinx/themes/haiku/static/haiku.css.jinja | 109 ++++++++ sphinx/themes/nature/static/nature.css.jinja | 162 +++++++++++ .../sphinxdoc/static/sphinxdoc.css.jinja | 181 ++++++++++++ 7 files changed, 1009 insertions(+) create mode 100644 sphinx/themes/basic/static/theme_toggle.js diff --git a/CHANGES.rst b/CHANGES.rst index f0f94fda396..681271bc6bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,6 +68,12 @@ Features added Patch by Jean-Franรงois B. * #13508: Initial support for :pep:`695` type aliases. Patch by Martin Matouลก, Jeremy Maitin-Shepard, and Adam Turner. +* #13742: HTML themes: Add dark mode support to built-in themes (``basic``, + ``classic``, ``haiku``, ``nature``, and ``sphinxdoc``). Dark mode + automatically activates based on system preferences using + ``prefers-color-scheme`` media query. An optional JavaScript toggle + (``theme_toggle.js``) is also provided for manual theme switching. + Patch by Fazeel Usmani. Bugs fixed ---------- diff --git a/sphinx/themes/basic/static/basic.css.jinja b/sphinx/themes/basic/static/basic.css.jinja index d2411760b18..1e09baf444e 100644 --- a/sphinx/themes/basic/static/basic.css.jinja +++ b/sphinx/themes/basic/static/basic.css.jinja @@ -904,3 +904,266 @@ div.math:hover a.headerlink { display: none; } } + +/* -- theme toggle button --------------------------------------------------- */ + +.theme-toggle-button { + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px 10px; + cursor: pointer; + font-size: 1.2em; + transition: background-color 0.3s ease; +} + +.theme-toggle-button:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +.theme-toggle-icon { + display: inline-block; +} + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #121212; + color: #e0e0e0; + } + + a { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + ul.search li p.context { + color: #aaa; + } + + table.indextable tr.cap { + background-color: #2a2a2a; + } + + div.modindex-jumpbox, + div.genindex-jumpbox { + border-top: 1px solid #444; + border-bottom: 1px solid #444; + } + + div.sidebar, + aside.sidebar { + border: 1px solid #555; + background-color: #2a2a2a; + } + + nav.contents, + aside.topic, + div.topic { + border: 1px solid #444; + background-color: #1e1e1e; + } + + div.admonition { + background-color: #1e1e1e; + border: 1px solid #444; + } + + table.docutils td, + table.docutils th { + border-bottom: 1px solid #555; + } + + table.citation { + border-left: solid 1px #666; + } + + dt:target, + span.highlighted { + background-color: #4a4a00; + } + + rect.highlighted { + fill: #4a4a00; + } + + .system-message { + background-color: #4a1f1f; + border: 3px solid #a00; + } + + .footnote:target { + background-color: #4a4a00; + } + + code, + pre { + background-color: #1e1e1e; + color: #f1f1f1; + } + + div.code-block-caption { + background-color: #2a2a2a; + } + + td.linenos pre { + background-color: transparent; + color: #666; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + } + + .sig.c .k, .sig.c .kt, + .sig.cpp .k, .sig.cpp .kt { + color: #6db3f2; + } + + .sig.c .m, + .sig.cpp .m { + color: #80cbc4; + } + + .sig.c .s, .sig.c .sc, + .sig.cpp .s, .sig.cpp .sc { + color: #90ee90; + } +} + +/* -- manual dark mode (via data-theme attribute) --------------------------- */ + +[data-theme="dark"] body { + background-color: #121212; + color: #e0e0e0; +} + +[data-theme="dark"] a { + color: #80cbc4; +} + +[data-theme="dark"] a:visited { + color: #ce93d8; +} + +[data-theme="dark"] div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; +} + +[data-theme="dark"] ul.search li p.context { + color: #aaa; +} + +[data-theme="dark"] table.indextable tr.cap { + background-color: #2a2a2a; +} + +[data-theme="dark"] div.modindex-jumpbox, +[data-theme="dark"] div.genindex-jumpbox { + border-top: 1px solid #444; + border-bottom: 1px solid #444; +} + +[data-theme="dark"] div.sidebar, +[data-theme="dark"] aside.sidebar { + border: 1px solid #555; + background-color: #2a2a2a; +} + +[data-theme="dark"] nav.contents, +[data-theme="dark"] aside.topic, +[data-theme="dark"] div.topic { + border: 1px solid #444; + background-color: #1e1e1e; +} + +[data-theme="dark"] div.admonition { + background-color: #1e1e1e; + border: 1px solid #444; +} + +[data-theme="dark"] table.docutils td, +[data-theme="dark"] table.docutils th { + border-bottom: 1px solid #555; +} + +[data-theme="dark"] table.citation { + border-left: solid 1px #666; +} + +[data-theme="dark"] dt:target, +[data-theme="dark"] span.highlighted { + background-color: #4a4a00; +} + +[data-theme="dark"] rect.highlighted { + fill: #4a4a00; +} + +[data-theme="dark"] .system-message { + background-color: #4a1f1f; + border: 3px solid #a00; +} + +[data-theme="dark"] .footnote:target { + background-color: #4a4a00; +} + +[data-theme="dark"] code, +[data-theme="dark"] pre { + background-color: #1e1e1e; + color: #f1f1f1; +} + +[data-theme="dark"] div.code-block-caption { + background-color: #2a2a2a; +} + +[data-theme="dark"] td.linenos pre { + background-color: transparent; + color: #666; +} + +[data-theme="dark"] div.viewcode-block:target { + background-color: #3a3a2a; +} + +[data-theme="dark"] .sig.c .k, +[data-theme="dark"] .sig.c .kt, +[data-theme="dark"] .sig.cpp .k, +[data-theme="dark"] .sig.cpp .kt { + color: #6db3f2; +} + +[data-theme="dark"] .sig.c .m, +[data-theme="dark"] .sig.cpp .m { + color: #80cbc4; +} + +[data-theme="dark"] .sig.c .s, +[data-theme="dark"] .sig.c .sc, +[data-theme="dark"] .sig.cpp .s, +[data-theme="dark"] .sig.cpp .sc { + color: #90ee90; +} + +[data-theme="dark"] .theme-toggle-button { + background-color: rgba(0, 0, 0, 0.3); + border-color: #555; +} + +[data-theme="dark"] .theme-toggle-button:hover { + background-color: rgba(0, 0, 0, 0.5); +} diff --git a/sphinx/themes/basic/static/theme_toggle.js b/sphinx/themes/basic/static/theme_toggle.js new file mode 100644 index 00000000000..1a1a51fff78 --- /dev/null +++ b/sphinx/themes/basic/static/theme_toggle.js @@ -0,0 +1,126 @@ +/** + * Sphinx theme toggle - Manual dark/light mode switcher + * + * This script provides a toggle button to manually switch between light and dark modes, + * overriding the system preference when desired. + */ + +(function() { + 'use strict'; + + // Check for saved theme preference or default to system preference + function getThemePreference() { + const saved = localStorage.getItem('sphinx-theme'); + if (saved) { + return saved; + } + // Check system preference + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + return 'dark'; + } + return 'light'; + } + + // Apply theme to document + function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('sphinx-theme', theme); + } + + // Create and insert toggle button + function createToggleButton() { + const button = document.createElement('button'); + button.id = 'theme-toggle'; + button.className = 'theme-toggle-button'; + button.setAttribute('aria-label', 'Toggle dark mode'); + button.title = 'Toggle dark/light mode'; + + // Add button text/icon + button.innerHTML = '๐ŸŒ™'; + + button.addEventListener('click', function() { + const current = document.documentElement.getAttribute('data-theme') || 'light'; + const next = current === 'dark' ? 'light' : 'dark'; + applyTheme(next); + updateButtonIcon(next); + }); + + // Insert button into the page (try multiple locations) + const insertLocations = [ + '.sphinxsidebar', + '.related', + 'body' + ]; + + for (const selector of insertLocations) { + const container = document.querySelector(selector); + if (container) { + if (selector === '.sphinxsidebar') { + // Insert at top of sidebar + container.insertBefore(button, container.firstChild); + } else if (selector === '.related') { + // Insert into navigation + const nav = container.querySelector('ul'); + if (nav) { + const li = document.createElement('li'); + li.className = 'right'; + li.appendChild(button); + nav.appendChild(li); + } + } else { + // Fallback: fixed position button + button.style.position = 'fixed'; + button.style.bottom = '20px'; + button.style.right = '20px'; + button.style.zIndex = '1000'; + container.appendChild(button); + } + break; + } + } + + return button; + } + + // Update button icon based on current theme + function updateButtonIcon(theme) { + const button = document.getElementById('theme-toggle'); + if (button) { + const icon = button.querySelector('.theme-toggle-icon'); + if (icon) { + icon.textContent = theme === 'dark' ? 'โ˜€๏ธ' : '๐ŸŒ™'; + } + } + } + + // Initialize on page load + function init() { + const theme = getThemePreference(); + applyTheme(theme); + + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + createToggleButton(); + updateButtonIcon(theme); + }); + } else { + createToggleButton(); + updateButtonIcon(theme); + } + + // Listen for system theme changes + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { + // Only apply if user hasn't set a manual preference + if (!localStorage.getItem('sphinx-theme')) { + const newTheme = e.matches ? 'dark' : 'light'; + applyTheme(newTheme); + updateButtonIcon(newTheme); + } + }); + } + } + + init(); +})(); diff --git a/sphinx/themes/classic/static/classic.css.jinja b/sphinx/themes/classic/static/classic.css.jinja index 24b67fa76ed..cf6daf01dc8 100644 --- a/sphinx/themes/classic/static/classic.css.jinja +++ b/sphinx/themes/classic/static/classic.css.jinja @@ -344,3 +344,165 @@ div.code-block-caption { color: #efefef; background-color: #1c4e63; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + html { + background-color: #1a1a1a; + } + + body { + background-color: #1e1e1e; + color: #e0e0e0; + } + + div.document { + background-color: #252525; + } + + div.body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.footer { + color: #aaa; + } + + div.footer a { + color: #aaa; + } + + div.related { + background-color: #2a2a2a; + color: #e0e0e0; + } + + div.related a { + color: #80cbc4; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h3 a, + div.sphinxsidebar h4, + div.sphinxsidebar p, + div.sphinxsidebar ul { + color: #e0e0e0; + } + + div.sphinxsidebar a { + color: #80cbc4; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + {% if theme_collapsiblesidebar|tobool %} + #sidebarbutton { + background-color: #3a3a3a; + color: #e0e0e0; + border-{{side}}: 1px solid #2a2a2a; + } + + #sidebarbutton:hover { + background-color: #2a2a2a; + } + {% endif %} + + a { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + {% if theme_externalrefs|tobool %} + a.external { + border-bottom: 1px dashed #80cbc4; + } + + a.external:visited { + border-bottom: 1px dashed #ce93d8; + } + {% endif %} + + div.body h1, + div.body h2, + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #252525; + color: #6db3f2; + border-bottom: 1px solid #444; + } + + a.headerlink { + color: #80cbc4; + } + + a.headerlink:hover { + background-color: #80cbc4; + color: #1a1a1a; + } + + div.note { + background-color: #1e3a1e; + border: 1px solid #444; + } + + div.seealso { + background-color: #3a3a1e; + border: 1px solid #666; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #252525; + } + + div.warning { + background-color: #3a1e1e; + border: 1px solid #a66; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #555; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + th, dl.field-list > dt { + background-color: #2a2a2a; + } + + .warning code { + background: #4a2a2a; + } + + .note code { + background: #2a3a2a; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + color: #e0e0e0; + background-color: #2a4a5a; + } +} diff --git a/sphinx/themes/haiku/static/haiku.css.jinja b/sphinx/themes/haiku/static/haiku.css.jinja index 676e34ceeaf..5907358ea83 100644 --- a/sphinx/themes/haiku/static/haiku.css.jinja +++ b/sphinx/themes/haiku/static/haiku.css.jinja @@ -367,3 +367,112 @@ div.viewcode-block:target { div.math p { text-align: center; } + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + html { + background: #1a1a1a url(bg-page.png) top left repeat-x; + } + + body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.header h1, + div.header h1 a, + h1, h2, h3, h4 { + color: #6db3f2; + } + + div.header h2 { + color: #888; + } + + div.title { + color: #6db3f2; + border-bottom: dotted thin #444; + } + + div.bottomnav { + background: #2a2a2a; + } + + a:link { + color: #ff7742; + } + + a:visited { + color: #b388ff; + } + + a:hover, a:active { + color: #ffa06e; + } + + a.headerlink { + color: #a7ce38; + } + + table.index { + border-color: #444; + } + + table.index tr.heading { + background-color: #2a2a2a; + } + + table.index tr.index { + background-color: #222; + } + + table.index a:link { + color: #ff7742; + } + + table.index a:hover, table.index a:active { + color: #ffa06e; + } + + div.admonition { + border-color: #444; + background-color: #252525; + } + + div.note { + background: #1e3a1e url(alert_info_32.png) 15px 15px no-repeat; + } + + div.warning { + background: #3a3a1e url(alert_warning_32.png) 15px 15px no-repeat; + } + + div.seealso { + background: #1e3a1e; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + pre { + background-color: #1e1e1e; + border-color: #6db3f2; + color: #e0e0e0; + } + + hr { + border-top-color: #444; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + h1, h2 { + border-bottom: dotted thin #444; + } +} diff --git a/sphinx/themes/nature/static/nature.css.jinja b/sphinx/themes/nature/static/nature.css.jinja index 3c8205382c6..56237affbf1 100644 --- a/sphinx/themes/nature/static/nature.css.jinja +++ b/sphinx/themes/nature/static/nature.css.jinja @@ -243,3 +243,165 @@ div.code-block-caption { color: #222; border: 1px solid #C6C9CB; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #1a1a1a; + color: #b0b0b0; + } + + hr { + border: 1px solid #555; + } + + div.document { + background-color: #252525; + } + + div.body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.footer { + color: #aaa; + } + + div.footer a { + color: #aaa; + } + + div.related { + background-color: #2a5a1e; + color: #e0e0e0; + text-shadow: 0px 1px 0 #000; + } + + div.related a { + color: #b8e699; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h4 { + color: #e0e0e0; + background-color: #2a2a2a; + text-shadow: 1px 1px 0 #000; + } + + div.sphinxsidebar h3 a { + color: #e0e0e0; + } + + div.sphinxsidebar p { + color: #aaa; + } + + div.sphinxsidebar ul { + color: #e0e0e0; + } + + div.sphinxsidebar a { + color: #80cbc4; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + a { + color: #80cbc4; + } + + a:hover { + color: #ffa06e; + } + + a:visited { + color: #ce93d8; + } + + div.body h1, + div.body h2, + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #2a4a5a; + color: #6db3f2; + text-shadow: 0px 1px 0 #000; + } + + div.body h1 { + border-top: 20px solid #1a1a1a; + } + + div.body h2 { + background-color: #2a3a4a; + } + + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #2a3a3a; + } + + a.headerlink { + color: #ff7742; + } + + a.headerlink:hover { + background-color: #ff7742; + color: #1a1a1a; + } + + div.note { + background-color: #252525; + border: 1px solid #444; + } + + div.seealso { + background-color: #3a3a1e; + border: 1px solid #666; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #252525; + } + + div.warning { + background-color: #3a1e1e; + border: 1px solid #a66; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #444; + -webkit-box-shadow: 1px 1px 1px #000; + -moz-box-shadow: 1px 1px 1px #000; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + background-color: #2a2a2a; + color: #e0e0e0; + border: 1px solid #444; + } +} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja b/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja index 07a0166d012..b8f50560ff8 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja @@ -347,3 +347,184 @@ div.code-block-caption { color: #222; border: 1px solid #ccc; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #2a3a3d; + color: #e0e0e0; + border: 1px solid #555; + } + + div.document { + background-color: #1a1a1a; + background-image: none; + } + + div.bodywrapper { + border-right: 1px solid #444; + } + + div.related ul { + background-image: none; + background-color: #2a2a2a; + border-top: 1px solid #444; + border-bottom: 1px solid #444; + } + + div.related ul li a { + color: #ffb347; + } + + div.related ul li a:hover { + color: #80cbc4; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h4 { + color: #e0e0e0; + border: 1px solid #555; + background-color: #3a4a4d; + } + + div.sphinxsidebar h3 a { + color: #e0e0e0; + } + + div.footer { + background-color: #2a3a3d; + color: #888; + } + + div.footer a { + color: #888; + } + + a { + color: #ffb347; + } + + a:hover { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + h1 { + color: #6db3f2; + } + + div.body h1 a, + div.body h2 a, + div.body h3 a, + div.body h4 a, + div.body h5 a, + div.body h6 a { + color: #e0e0e0 !important; + } + + h1 a.anchor, + h2 a.anchor, + h3 a.anchor, + h4 a.anchor, + h5 a.anchor, + h6 a.anchor { + color: #666 !important; + } + + h1 a.anchor:hover, + h2 a.anchor:hover, + h3 a.anchor:hover, + h4 a.anchor:hover, + h5 a.anchor:hover, + h6 a.anchor:hover { + color: #aaa; + background-color: #2a2a2a; + } + + a.headerlink { + color: #ff7742 !important; + } + + a.headerlink:hover { + background-color: #444; + color: #e0e0e0 !important; + } + + code { + background-color: #2a2a2a; + border-bottom: 1px solid #444; + color: #f1f1f1; + } + + hr { + border: 1px solid #555; + } + + a code { + color: #ffb347; + } + + a code:hover { + color: #80cbc4; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #444; + } + + div.quotebar { + background-color: #2a2a2a; + border: 1px solid #444; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #2a2a2a; + } + + div.admonition, + div.warning { + border: 1px solid #555; + background-color: #252525; + } + + div.admonition p.admonition-title, + div.warning p.admonition-title { + color: #e0e0e0; + border-bottom: 1px solid #555; + background-color: #3a4a4d; + } + + div.warning { + border: 1px solid #a66; + } + + div.warning p.admonition-title { + background-color: #8a2020; + border-bottom-color: #a66; + } + + div.versioninfo { + border: 1px solid #444; + background-color: #2a4a5a; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + background-color: #2a2a2a; + color: #e0e0e0; + border: 1px solid #444; + } +} From dee1a969eb4e7a3bc7897f18249a128dee92eeca Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Tue, 11 Nov 2025 15:34:45 +0530 Subject: [PATCH 2/3] Update theme_toggle.js --- sphinx/themes/basic/static/theme_toggle.js | 204 +++++++++++---------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/sphinx/themes/basic/static/theme_toggle.js b/sphinx/themes/basic/static/theme_toggle.js index 1a1a51fff78..ddc4ab314a4 100644 --- a/sphinx/themes/basic/static/theme_toggle.js +++ b/sphinx/themes/basic/static/theme_toggle.js @@ -5,122 +5,124 @@ * overriding the system preference when desired. */ -(function() { - 'use strict'; +(function () { + "use strict"; - // Check for saved theme preference or default to system preference - function getThemePreference() { - const saved = localStorage.getItem('sphinx-theme'); - if (saved) { - return saved; - } - // Check system preference - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - return 'dark'; - } - return 'light'; + // Check for saved theme preference or default to system preference + function getThemePreference() { + const saved = localStorage.getItem("sphinx-theme"); + if (saved) { + return saved; } - - // Apply theme to document - function applyTheme(theme) { - document.documentElement.setAttribute('data-theme', theme); - localStorage.setItem('sphinx-theme', theme); + // Check system preference + if ( + window.matchMedia + && window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + return "dark"; } + return "light"; + } - // Create and insert toggle button - function createToggleButton() { - const button = document.createElement('button'); - button.id = 'theme-toggle'; - button.className = 'theme-toggle-button'; - button.setAttribute('aria-label', 'Toggle dark mode'); - button.title = 'Toggle dark/light mode'; + // Apply theme to document + function applyTheme(theme) { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("sphinx-theme", theme); + } - // Add button text/icon - button.innerHTML = '๐ŸŒ™'; + // Create and insert toggle button + function createToggleButton() { + const button = document.createElement("button"); + button.id = "theme-toggle"; + button.className = "theme-toggle-button"; + button.setAttribute("aria-label", "Toggle dark mode"); + button.title = "Toggle dark/light mode"; - button.addEventListener('click', function() { - const current = document.documentElement.getAttribute('data-theme') || 'light'; - const next = current === 'dark' ? 'light' : 'dark'; - applyTheme(next); - updateButtonIcon(next); - }); + // Add button text/icon + button.innerHTML = '๐ŸŒ™'; - // Insert button into the page (try multiple locations) - const insertLocations = [ - '.sphinxsidebar', - '.related', - 'body' - ]; + button.addEventListener("click", function () { + const current = + document.documentElement.getAttribute("data-theme") || "light"; + const next = current === "dark" ? "light" : "dark"; + applyTheme(next); + updateButtonIcon(next); + }); - for (const selector of insertLocations) { - const container = document.querySelector(selector); - if (container) { - if (selector === '.sphinxsidebar') { - // Insert at top of sidebar - container.insertBefore(button, container.firstChild); - } else if (selector === '.related') { - // Insert into navigation - const nav = container.querySelector('ul'); - if (nav) { - const li = document.createElement('li'); - li.className = 'right'; - li.appendChild(button); - nav.appendChild(li); - } - } else { - // Fallback: fixed position button - button.style.position = 'fixed'; - button.style.bottom = '20px'; - button.style.right = '20px'; - button.style.zIndex = '1000'; - container.appendChild(button); - } - break; - } - } + // Insert button into the page (try multiple locations) + const insertLocations = [".sphinxsidebar", ".related", "body"]; - return button; + for (const selector of insertLocations) { + const container = document.querySelector(selector); + if (container) { + if (selector === ".sphinxsidebar") { + // Insert at top of sidebar + container.insertBefore(button, container.firstChild); + } else if (selector === ".related") { + // Insert into navigation + const nav = container.querySelector("ul"); + if (nav) { + const li = document.createElement("li"); + li.className = "right"; + li.appendChild(button); + nav.appendChild(li); + } + } else { + // Fallback: fixed position button + button.style.position = "fixed"; + button.style.bottom = "20px"; + button.style.right = "20px"; + button.style.zIndex = "1000"; + container.appendChild(button); + } + break; + } } - // Update button icon based on current theme - function updateButtonIcon(theme) { - const button = document.getElementById('theme-toggle'); - if (button) { - const icon = button.querySelector('.theme-toggle-icon'); - if (icon) { - icon.textContent = theme === 'dark' ? 'โ˜€๏ธ' : '๐ŸŒ™'; - } - } + return button; + } + + // Update button icon based on current theme + function updateButtonIcon(theme) { + const button = document.getElementById("theme-toggle"); + if (button) { + const icon = button.querySelector(".theme-toggle-icon"); + if (icon) { + icon.textContent = theme === "dark" ? "โ˜€๏ธ" : "๐ŸŒ™"; + } } + } - // Initialize on page load - function init() { - const theme = getThemePreference(); - applyTheme(theme); + // Initialize on page load + function init() { + const theme = getThemePreference(); + applyTheme(theme); - // Wait for DOM to be ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - createToggleButton(); - updateButtonIcon(theme); - }); - } else { - createToggleButton(); - updateButtonIcon(theme); - } + // Wait for DOM to be ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", function () { + createToggleButton(); + updateButtonIcon(theme); + }); + } else { + createToggleButton(); + updateButtonIcon(theme); + } - // Listen for system theme changes - if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { - // Only apply if user hasn't set a manual preference - if (!localStorage.getItem('sphinx-theme')) { - const newTheme = e.matches ? 'dark' : 'light'; - applyTheme(newTheme); - updateButtonIcon(newTheme); - } - }); - } + // Listen for system theme changes + if (window.matchMedia) { + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", function (e) { + // Only apply if user hasn't set a manual preference + if (!localStorage.getItem("sphinx-theme")) { + const newTheme = e.matches ? "dark" : "light"; + applyTheme(newTheme); + updateButtonIcon(newTheme); + } + }); } + } - init(); + init(); })(); From 1b9ef4965b312c0f59c6ac02f76846273a773f1b Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Mon, 24 Nov 2025 11:51:41 +0530 Subject: [PATCH 3/3] Refactor dark mode implementation based on review feedback --- CHANGES.rst | 7 +- doc/_themes/sphinx13/static/sphinx13.css | 85 ++++++++++++ sphinx/themes/basic/static/basic.css.jinja | 147 --------------------- sphinx/themes/basic/static/theme_toggle.js | 128 ------------------ 4 files changed, 88 insertions(+), 279 deletions(-) delete mode 100644 sphinx/themes/basic/static/theme_toggle.js diff --git a/CHANGES.rst b/CHANGES.rst index 681271bc6bc..d87224fb639 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -69,10 +69,9 @@ Features added * #13508: Initial support for :pep:`695` type aliases. Patch by Martin Matouลก, Jeremy Maitin-Shepard, and Adam Turner. * #13742: HTML themes: Add dark mode support to built-in themes (``basic``, - ``classic``, ``haiku``, ``nature``, and ``sphinxdoc``). Dark mode - automatically activates based on system preferences using - ``prefers-color-scheme`` media query. An optional JavaScript toggle - (``theme_toggle.js``) is also provided for manual theme switching. + ``classic``, ``haiku``, ``nature``, ``sphinxdoc``, and ``sphinx13``). + Dark mode automatically activates based on system preferences using the + ``prefers-color-scheme`` media query. Patch by Fazeel Usmani. Bugs fixed diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css index 22bef884737..9c8457c6337 100644 --- a/doc/_themes/sphinx13/static/sphinx13.css +++ b/doc/_themes/sphinx13/static/sphinx13.css @@ -785,3 +785,88 @@ ul.search li.kind-title { ul.search li.kind-text { list-style-type: "\1F4C4"; /* Unicode: Page Facing Up */ } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + :root { + --colour-sphinx-blue: #1a7db8; + --colour-text: #e0e0e0; + --colour-links-light: #80cbc4; + + /* darker backgrounds for admonitions */ + --color-admonition-bg: hsl(0, 0%, 20%); + --color-admonition-fg: hsl(0, 0%, 70%); + --colour-warning-bg: hsl(28.5, 74%, 20%); + --colour-warning-fg: hsl(28.5, 74%, 70%); + --colour-note-bg: hsl(219.5, 84%, 20%); + --colour-note-fg: hsl(219.5, 84%, 70%); + --colour-success-bg: hsl(150, 36.7%, 20%); + --colour-success-fg: hsl(150, 36.7%, 70%); + --colour-error-bg: hsl(0, 37%, 20%); + --colour-error-fg: hsl(0, 37%, 70%); + --colour-todo-bg: hsl(266.8, 100%, 20%); + --colour-todo-fg: hsl(266.8, 100%, 70%); + } + + body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + a { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + pre { + background-color: #1e1e1e; + border-color: #444; + } + + div.body { + border-left-color: #1a7db8; + } + + div.related { + background-color: #0a3a5a; + border-top-color: #081f35; + } + + div.sphinxsidebar { + background-color: #1e1e1e; + } + + div.sphinxsidebar input[type="text"] { + background-color: #2a2a2a; + border-color: #555; + color: #e0e0e0; + } + + table.docutils td, + table.docutils th { + border-color: #555; + } + + table.docutils thead th { + background-color: #2a2a2a; + } + + dt:target, + span.highlighted { + background-color: #4a4a00; + } + + div.topic { + background-color: #252525; + border-color: #444; + } +} diff --git a/sphinx/themes/basic/static/basic.css.jinja b/sphinx/themes/basic/static/basic.css.jinja index 1e09baf444e..e724bd9887a 100644 --- a/sphinx/themes/basic/static/basic.css.jinja +++ b/sphinx/themes/basic/static/basic.css.jinja @@ -905,26 +905,6 @@ div.math:hover a.headerlink { } } -/* -- theme toggle button --------------------------------------------------- */ - -.theme-toggle-button { - background-color: rgba(255, 255, 255, 0.1); - border: 1px solid #ccc; - border-radius: 4px; - padding: 5px 10px; - cursor: pointer; - font-size: 1.2em; - transition: background-color 0.3s ease; -} - -.theme-toggle-button:hover { - background-color: rgba(255, 255, 255, 0.2); -} - -.theme-toggle-icon { - display: inline-block; -} - /* -- dark mode support ----------------------------------------------------- */ @media (prefers-color-scheme: dark) { @@ -1040,130 +1020,3 @@ div.math:hover a.headerlink { color: #90ee90; } } - -/* -- manual dark mode (via data-theme attribute) --------------------------- */ - -[data-theme="dark"] body { - background-color: #121212; - color: #e0e0e0; -} - -[data-theme="dark"] a { - color: #80cbc4; -} - -[data-theme="dark"] a:visited { - color: #ce93d8; -} - -[data-theme="dark"] div.sphinxsidebar input { - border: 1px solid #555; - background-color: #2a2a2a; - color: #e0e0e0; -} - -[data-theme="dark"] ul.search li p.context { - color: #aaa; -} - -[data-theme="dark"] table.indextable tr.cap { - background-color: #2a2a2a; -} - -[data-theme="dark"] div.modindex-jumpbox, -[data-theme="dark"] div.genindex-jumpbox { - border-top: 1px solid #444; - border-bottom: 1px solid #444; -} - -[data-theme="dark"] div.sidebar, -[data-theme="dark"] aside.sidebar { - border: 1px solid #555; - background-color: #2a2a2a; -} - -[data-theme="dark"] nav.contents, -[data-theme="dark"] aside.topic, -[data-theme="dark"] div.topic { - border: 1px solid #444; - background-color: #1e1e1e; -} - -[data-theme="dark"] div.admonition { - background-color: #1e1e1e; - border: 1px solid #444; -} - -[data-theme="dark"] table.docutils td, -[data-theme="dark"] table.docutils th { - border-bottom: 1px solid #555; -} - -[data-theme="dark"] table.citation { - border-left: solid 1px #666; -} - -[data-theme="dark"] dt:target, -[data-theme="dark"] span.highlighted { - background-color: #4a4a00; -} - -[data-theme="dark"] rect.highlighted { - fill: #4a4a00; -} - -[data-theme="dark"] .system-message { - background-color: #4a1f1f; - border: 3px solid #a00; -} - -[data-theme="dark"] .footnote:target { - background-color: #4a4a00; -} - -[data-theme="dark"] code, -[data-theme="dark"] pre { - background-color: #1e1e1e; - color: #f1f1f1; -} - -[data-theme="dark"] div.code-block-caption { - background-color: #2a2a2a; -} - -[data-theme="dark"] td.linenos pre { - background-color: transparent; - color: #666; -} - -[data-theme="dark"] div.viewcode-block:target { - background-color: #3a3a2a; -} - -[data-theme="dark"] .sig.c .k, -[data-theme="dark"] .sig.c .kt, -[data-theme="dark"] .sig.cpp .k, -[data-theme="dark"] .sig.cpp .kt { - color: #6db3f2; -} - -[data-theme="dark"] .sig.c .m, -[data-theme="dark"] .sig.cpp .m { - color: #80cbc4; -} - -[data-theme="dark"] .sig.c .s, -[data-theme="dark"] .sig.c .sc, -[data-theme="dark"] .sig.cpp .s, -[data-theme="dark"] .sig.cpp .sc { - color: #90ee90; -} - -[data-theme="dark"] .theme-toggle-button { - background-color: rgba(0, 0, 0, 0.3); - border-color: #555; -} - -[data-theme="dark"] .theme-toggle-button:hover { - background-color: rgba(0, 0, 0, 0.5); -} diff --git a/sphinx/themes/basic/static/theme_toggle.js b/sphinx/themes/basic/static/theme_toggle.js deleted file mode 100644 index ddc4ab314a4..00000000000 --- a/sphinx/themes/basic/static/theme_toggle.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Sphinx theme toggle - Manual dark/light mode switcher - * - * This script provides a toggle button to manually switch between light and dark modes, - * overriding the system preference when desired. - */ - -(function () { - "use strict"; - - // Check for saved theme preference or default to system preference - function getThemePreference() { - const saved = localStorage.getItem("sphinx-theme"); - if (saved) { - return saved; - } - // Check system preference - if ( - window.matchMedia - && window.matchMedia("(prefers-color-scheme: dark)").matches - ) { - return "dark"; - } - return "light"; - } - - // Apply theme to document - function applyTheme(theme) { - document.documentElement.setAttribute("data-theme", theme); - localStorage.setItem("sphinx-theme", theme); - } - - // Create and insert toggle button - function createToggleButton() { - const button = document.createElement("button"); - button.id = "theme-toggle"; - button.className = "theme-toggle-button"; - button.setAttribute("aria-label", "Toggle dark mode"); - button.title = "Toggle dark/light mode"; - - // Add button text/icon - button.innerHTML = '๐ŸŒ™'; - - button.addEventListener("click", function () { - const current = - document.documentElement.getAttribute("data-theme") || "light"; - const next = current === "dark" ? "light" : "dark"; - applyTheme(next); - updateButtonIcon(next); - }); - - // Insert button into the page (try multiple locations) - const insertLocations = [".sphinxsidebar", ".related", "body"]; - - for (const selector of insertLocations) { - const container = document.querySelector(selector); - if (container) { - if (selector === ".sphinxsidebar") { - // Insert at top of sidebar - container.insertBefore(button, container.firstChild); - } else if (selector === ".related") { - // Insert into navigation - const nav = container.querySelector("ul"); - if (nav) { - const li = document.createElement("li"); - li.className = "right"; - li.appendChild(button); - nav.appendChild(li); - } - } else { - // Fallback: fixed position button - button.style.position = "fixed"; - button.style.bottom = "20px"; - button.style.right = "20px"; - button.style.zIndex = "1000"; - container.appendChild(button); - } - break; - } - } - - return button; - } - - // Update button icon based on current theme - function updateButtonIcon(theme) { - const button = document.getElementById("theme-toggle"); - if (button) { - const icon = button.querySelector(".theme-toggle-icon"); - if (icon) { - icon.textContent = theme === "dark" ? "โ˜€๏ธ" : "๐ŸŒ™"; - } - } - } - - // Initialize on page load - function init() { - const theme = getThemePreference(); - applyTheme(theme); - - // Wait for DOM to be ready - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", function () { - createToggleButton(); - updateButtonIcon(theme); - }); - } else { - createToggleButton(); - updateButtonIcon(theme); - } - - // Listen for system theme changes - if (window.matchMedia) { - window - .matchMedia("(prefers-color-scheme: dark)") - .addEventListener("change", function (e) { - // Only apply if user hasn't set a manual preference - if (!localStorage.getItem("sphinx-theme")) { - const newTheme = e.matches ? "dark" : "light"; - applyTheme(newTheme); - updateButtonIcon(newTheme); - } - }); - } - } - - init(); -})();