Skip to content

Commit a836155

Browse files
waylanoprypin
andcommitted
Add support for a 'dark mode' to mkdocs theme
Also add an optional toggle button for it. Three new config options have been added: 1. 'color_mode' which can be set to one of 'light', 'dark', or 'auto'. Default is 'light' for backward compatability. The 'auto' color mode will check the system settings and automaticaly switch to light or dark mode on page load or when the system's color mode changes. 2. 'hljs_style_dark': the Highlight.js theme to use in 'dark_mode'. Default is 'github-dark' which matches the light mode default of 'github'. The preexisting config option 'hljs_style' is used for 'light' mode. 3. 'user_color_mode_toggle': Allow users to select their prefered color mode (light, dark, auto) from within the browser and save their preference for future page loads. The new config option 'user_color_mode_toggle' (default: 'False') can be enabled to display a toggle menu in the nav bar. The default value of the toggle menu on first load is the value set to 'color_mode'. MkDocs' own documentation is now configured with 'color_mode: auto'. Co-authored-by: Oleh Prypin <oleh@pryp.in>
1 parent 694a602 commit a836155

File tree

11 files changed

+148
-19
lines changed

11 files changed

+148
-19
lines changed
8.82 KB
Loading

docs/img/mkdocs.png

-77.2 KB
Binary file not shown.
138 KB
Loading
144 KB
Loading

docs/user-guide/choosing-your-theme.md

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,45 @@ theme:
2121
The default theme, which was built as a custom [Bootstrap] theme, supports almost
2222
every feature of MkDocs.
2323
24-
![mkdocs](../img/mkdocs.png)
24+
<div id="mkdocs-theme-images" class="carousel slide carousel-fade" data-bs-ride="carousel">
25+
<div class="carousel-inner">
26+
<div class="carousel-item active">
27+
<img src="../../img/mkdocs_theme_light_mode.png" class="d-block w-100" alt="MkDocs theme in light mode">
28+
</div>
29+
<div class="carousel-item">
30+
<img src="../../img/mkdocs_theme_dark_mode.png" class="d-block w-100" alt="MkDocs theme in dark mode">
31+
</div>
32+
</div>
33+
</div>
2534
2635
In addition to the default [theme configuration options][theme], the `mkdocs` theme
2736
supports the following options:
2837

38+
* **`color_mode`**: Set the default color mode for the theme to one of `light`,
39+
`dark`, or `auto`. The `auto` mode will switch to `light` or `dark` based on
40+
the system configuration of the user's device. Default: `light`.
41+
42+
* **`user_color_mode_toggle`**: Enable a toggle menu in the navigation bar
43+
which allows users to select their preferred `color_mode` (light, dark, auto)
44+
from within the browser and save their preference for future page loads. The
45+
default selection of the toggle menu on first page load is the value set to
46+
`color_mode`. Default: `false`.
47+
48+
![color mode toggle menu](../img/color_mode_toggle_menu.png)
49+
50+
* **`nav_style`**: Adjust the visual style of the top navigation bar. Set to
51+
one of `primary`, `dark` or `light`. Default: `primary`. This option is
52+
independent of the `color_mode` option and must be defined separately.
53+
2954
* **`highlightjs`**: Enables highlighting of source code in code blocks using
3055
the [highlight.js] JavaScript library. Default: `True`.
3156

32-
* **`hljs_style`**: The highlight.js library provides 79 different [styles]
57+
* **`hljs_style`**: The highlight.js library provides many different [styles]
3358
(color variations) for highlighting source code in code blocks. Set this to
34-
the name of the desired style. Default: `github`.
59+
the name of the desired style when in `light` mode. Default: `github`.
60+
61+
* **`hljs_style_dark`**: Set this to the name of the desired highlight.js
62+
style when in `dark` mode. Default: `github_dark`.
3563

3664
* **`hljs_languages`**: By default, highlight.js only supports 23 common
3765
languages. List additional languages here to include support for them.
@@ -91,16 +119,6 @@ supports the following options:
91119
* **`navigation_depth`**: The maximum depth of the navigation tree in the
92120
sidebar. Default: `2`.
93121

94-
* **`nav_style`**: This adjusts the visual style for the top navigation bar; by
95-
default, this is set to `primary` (the default), but it can also be set to
96-
`dark` or `light`.
97-
98-
```yaml
99-
theme:
100-
name: mkdocs
101-
nav_style: dark
102-
```
103-
104122
* **`locale`**{ #mkdocs-locale }: The locale (language/location) used to
105123
build the theme. If your locale is not yet supported, it will fall back
106124
to the default.

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ edit_uri: blob/master/docs/
88

99
theme:
1010
name: mkdocs
11+
color_mode: auto
12+
user_color_mode_toggle: true
1113
locale: en
1214
analytics: {gtag: 'G-274394082'}
1315
highlightjs: true

mkdocs/tests/config/config_tests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,16 @@ def test_theme(self, mytheme, custom):
105105
'static_templates': ['404.html', 'sitemap.xml'],
106106
'vars': {
107107
'name': 'mkdocs',
108+
'color_mode': 'light',
109+
'user_color_mode_toggle': False,
108110
'locale': parse_locale('en'),
109111
'include_search_page': False,
110112
'search_index_only': False,
111113
'analytics': {'gtag': None},
112114
'highlightjs': True,
113115
'hljs_style': 'github',
114116
'hljs_languages': [],
117+
'hljs_style_dark': 'github-dark',
115118
'navigation_depth': 2,
116119
'nav_style': 'primary',
117120
'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83},
@@ -190,6 +193,8 @@ def test_theme(self, mytheme, custom):
190193
'static_templates': ['404.html', 'sitemap.xml', 'foo.html'],
191194
'vars': {
192195
'name': 'mkdocs',
196+
'color_mode': 'light',
197+
'user_color_mode_toggle': False,
193198
'locale': parse_locale('fr'),
194199
'show_sidebar': False,
195200
'some_var': 'bar',
@@ -199,6 +204,7 @@ def test_theme(self, mytheme, custom):
199204
'highlightjs': True,
200205
'hljs_style': 'github',
201206
'hljs_languages': [],
207+
'hljs_style_dark': 'github-dark',
202208
'navigation_depth': 2,
203209
'nav_style': 'primary',
204210
'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83},

mkdocs/tests/theme_tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ def test_simple_theme(self):
2525
dict(theme),
2626
{
2727
'name': 'mkdocs',
28+
'color_mode': 'light',
29+
'user_color_mode_toggle': False,
2830
'locale': parse_locale('en'),
2931
'include_search_page': False,
3032
'search_index_only': False,
3133
'analytics': {'gtag': None},
3234
'highlightjs': True,
3335
'hljs_style': 'github',
36+
'hljs_style_dark': 'github-dark',
3437
'hljs_languages': [],
3538
'navigation_depth': 2,
3639
'nav_style': 'primary',

mkdocs/themes/mkdocs/base.html

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="{{ config.theme.locale|default('en') }}" data-bs-theme="light">
2+
<html lang="{{ config.theme.locale|default('en') }}" data-bs-theme="{{ config.theme.color_mode }}">
33
<head>
44
{%- block site_meta %}
55
<meta charset="utf-8">
@@ -24,7 +24,8 @@
2424
<link href="{{ 'css/v4-font-face.min.css'|url }}" rel="stylesheet">
2525
<link href="{{ 'css/base.css'|url }}" rel="stylesheet">
2626
{%- if config.theme.highlightjs %}
27-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/{{ config.theme.hljs_style }}.min.css">
27+
<link id="hljs-light" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/{{ config.theme.hljs_style }}.min.css" {% if config.theme.color_mode != "light" %}disabled{% endif %}>
28+
<link id="hljs-dark" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/{{ config.theme.hljs_style_dark }}.min.css" {% if config.theme.color_mode != "dark" %}disabled{% endif %}>
2829
{%- endif %}
2930
{%- for path in config.extra_css %}
3031
<link href="{{ path|url }}" rel="stylesheet">
@@ -108,7 +109,7 @@
108109
{%- endif %}
109110
{%- endblock %}
110111

111-
<ul class="nav navbar-nav ml-auto">
112+
<ul class="nav navbar-nav ms-md-auto">
112113
{%- block search_button %}
113114
{%- if 'search' in config.plugins %}
114115
<li class="nav-item">
@@ -167,6 +168,37 @@
167168
</li>
168169
{%- endif %}
169170
{%- endblock %}
171+
{%- if config.theme.user_color_mode_toggle %}
172+
<li class="nav-item dropdown">
173+
<button id="theme-menu" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme" class="nav-link dropdown-toggle">
174+
<i class="fa-solid fa-circle-half-stroke fa-fw"></i>
175+
<span class="d-lg-none ms-2">Toggle theme</span>
176+
</button>
177+
<ul class="dropdown-menu dropdown-menu-end">
178+
<li>
179+
<button class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="{% if config.theme.color_mode == 'light' %}true{% else %}false{% endif %}">
180+
<i class="fa-solid fa-sun fa-fw"></i>
181+
<span class="ms-2">Light</span>
182+
<i class="fa-solid fa-check ms-auto{% if config.theme.color_mode != 'light' %} d-none{% endif %}"></i>
183+
</button>
184+
</li>
185+
<li>
186+
<button class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="{% if config.theme.color_mode == 'dark' %}true{% else %}false{% endif %}">
187+
<i class="fa-solid fa-moon fa-fw"></i>
188+
<span class="ms-2">Dark</span>
189+
<i class="fa-solid fa-check ms-auto{% if config.theme.color_mode != 'dark' %} d-none{% endif %}"></i>
190+
</button>
191+
</li>
192+
<li>
193+
<button class="dropdown-item d-flex align-items-center" data-bs-theme-value="auto" aria-pressed="{% if config.theme.color_mode == 'auto' %}true{% else %}false{% endif %}">
194+
<i class="fa-solid fa-circle-half-stroke fa-fw"></i>
195+
<span class="ms-2">Auto</span>
196+
<i class="fa-solid fa-check ms-auto{% if config.theme.color_mode != 'auto' %} d-none{% endif %}"></i>
197+
</button>
198+
</li>
199+
</ul>
200+
</li>
201+
{%- endif %}
170202
</ul>
171203
</div>
172204
</div>

mkdocs/themes/mkdocs/js/base.js

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ function applyTopPadding() {
1919
}
2020

2121
$(document).ready(function() {
22-
23-
applyTopPadding();
24-
2522
var search_term = getSearchTerm(),
2623
$search_modal = $('#mkdocs_search_modal'),
2724
$keyboard_modal = $('#mkdocs_keyboard_modal');
@@ -139,6 +136,8 @@ $(document).ready(function() {
139136
$(this).find('.dropdown-submenu > a').removeClass('open');
140137
$(this).find('.dropdown-menu .dropdown-menu').removeClass('show');
141138
});
139+
140+
applyTopPadding();
142141
});
143142

144143
$(window).on('resize', applyTopPadding);
@@ -267,3 +266,69 @@ var keyCodes = {
267266
221: '&rsqb;',
268267
222: '&apos;',
269268
};
269+
270+
function setColorMode(mode) {
271+
// Switch between light/dark theme. `mode` is a string value of either 'dark' or 'light'.
272+
var hljs_light = document.getElementById('hljs-light'),
273+
hljs_dark = document.getElementById('hljs-dark');
274+
document.documentElement.setAttribute('data-bs-theme', mode);
275+
if (mode == 'dark') {
276+
hljs_light.disabled = true;
277+
hljs_dark.disabled = false;
278+
} else {
279+
hljs_dark.disabled = true;
280+
hljs_light.disabled = false;
281+
}
282+
}
283+
284+
function updateModeToggle(mode) {
285+
// Update icon and toggle checkmarks of color mode selector.
286+
var menu = document.getElementById('theme-menu');
287+
document.querySelectorAll('[data-bs-theme-value]')
288+
.forEach(function(toggle) {
289+
if (mode == toggle.getAttribute('data-bs-theme-value')) {
290+
toggle.setAttribute('aria-pressed', 'true');
291+
toggle.lastElementChild.classList.remove('d-none');
292+
menu.firstElementChild.setAttribute('class', toggle.firstElementChild.getAttribute('class'));
293+
} else {
294+
toggle.setAttribute('aria-pressed', 'false');
295+
toggle.lastElementChild.classList.add('d-none');
296+
}
297+
});
298+
}
299+
300+
function onSystemColorSchemeChange(event) {
301+
// Update site color mode to match system color mode.
302+
setColorMode(event.matches ? 'dark' : 'light');
303+
}
304+
305+
var mql = window.matchMedia('(prefers-color-scheme: dark)'),
306+
defaultMode = document.documentElement.getAttribute('data-bs-theme'),
307+
storedMode = localStorage.getItem('mkdocs-colormode');
308+
if (storedMode && storedMode != 'auto') {
309+
setColorMode(storedMode);
310+
updateModeToggle(storedMode);
311+
} else if (storedMode == 'auto' || defaultMode == 'auto') {
312+
setColorMode(mql.matches ? 'dark' : 'light');
313+
updateModeToggle('auto');
314+
mql.addEventListener('change', onSystemColorSchemeChange);
315+
} else {
316+
setColorMode(defaultMode);
317+
updateModeToggle(defaultMode);
318+
}
319+
320+
document.querySelectorAll('[data-bs-theme-value]')
321+
.forEach(function(toggle) {
322+
toggle.addEventListener('click', function (e) {
323+
var mode = e.currentTarget.getAttribute('data-bs-theme-value');
324+
localStorage.setItem('mkdocs-colormode', mode);
325+
if (mode == 'auto') {
326+
setColorMode(mql.matches ? 'dark' : 'light');
327+
mql.addEventListener('change', onSystemColorSchemeChange);
328+
} else {
329+
setColorMode(mode);
330+
mql.removeEventListener('change', onSystemColorSchemeChange);
331+
}
332+
updateModeToggle(mode);
333+
});
334+
});

0 commit comments

Comments
 (0)