mkdocs-light-dark-toggle
A MkDocs plugin (built for and tested with Material for MkDocs) that replaces Material's native light/dark switch with an always-visible two-button toggle. Material's own switch is a single knob: the current scheme is the only thing you can see, and the knob itself is the only clickable spot. This plugin shows both options side by side, with a sliding highlight behind whichever is active, so switching is a single click on either option rather than a click-to-cycle knob.
The native palette radios stay in the DOM, hidden. Their own JavaScript still applies and persists
the scheme via localStorage — this plugin only drives them, it doesn't reimplement scheme
switching.
Light mode, sun active:
Dark mode, moon active:
plugins:
- light_dark_toggle
That's it — the toggle needs no configuration to work, since it ships with built-in sun/moon icons and sensible default text.
Requirements
Python 3.9+ and MkDocs 1.5+. Built for Material for MkDocs: the plugin looks for Material's own
[data-md-component="palette"] form and the two radio inputs inside it
(input[data-md-color-scheme="default"] and ="slate"). Your mkdocs.yml needs both schemes
configured under theme.palette:
theme:
name: material
palette:
- scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
If either radio isn't found, the plugin leaves Material's native switch alone.
Configure
plugins:
- light_dark_toggle:
light:
description: Switch to light mode
announcement: Lights on
dark:
description: Switch to dark mode
announcement: Lights off
| Key | Default | Description |
|---|---|---|
light |
see below | description/announcement for the light option. |
dark |
see below | description/announcement for the dark option. |
aria_label |
Color theme |
Accessible label for the toggle group. |
show_toast |
true |
Show a short message after the scheme changes. |
light/dark each take:
| Key | Default | Description |
|---|---|---|
description |
Switch to light/dark mode |
Button title and accessible label. |
announcement |
Light mode/Dark mode |
Text shown in the toast after switching to this scheme. |
Styling
The toggle's CSS is controlled with custom properties. Override them in your extra_css file on
#light-dark-toggle, or on a parent element such as :root:
#light-dark-toggle {
--light-dark-accent: #2e7d32; /* border and highlight color (default: currentColor) */
--light-dark-track-bg: #fdf6e3; /* toggle background (default: transparent) */
--light-dark-active-fg: #fdf6e3; /* icon color of the active option (default: Canvas) */
--light-dark-radius: 1rem; /* corner radius of the toggle and highlight (default: 1rem) */
--light-dark-height: 1.2rem; /* toggle height (default: 1.2rem) */
--light-dark-icon-size: 0.7rem; /* icon size (default: 0.7rem) */
}
Icons are CSS mask-image values and default to a built-in sun and moon:
#light-dark-toggle {
--light-dark-icon-light: url("data:image/svg+xml,...");
--light-dark-icon-dark: url("data:image/svg+xml,...");
}
The toast has its own properties, kept separate from --light-dark-accent/--light-dark-active-fg
so theming the toggle doesn't also recolor the toast:
#light-dark-toast {
--light-dark-toast-bg: #2e7d32; /* toast background (default: CanvasText) */
--light-dark-toast-fg: #fdf6e3; /* toast text color (default: Canvas) */
}
Left at their defaults, CanvasText/Canvas auto-invert the toast against the page's
color-scheme CSS property. If your site switches schemes manually rather than relying on
prefers-color-scheme — which, using this plugin, it does — set color-scheme: light/dark
yourself on the selector your palette CSS already scopes to (e.g.
[data-md-color-scheme="slate"] { color-scheme: dark; }) for that to track the active scheme
instead of the OS preference.
For other changes, target the classes .light-dark-toggle, .light-dark-highlight,
.light-dark-option, and .light-dark-toast. The script sets the highlight's position via
[data-active] on .light-dark-toggle, not inline styles.
Accessibility
- The color properties aren't checked for contrast. Check your color choices against WCAG contrast requirements.
- Each option is a toggle button with
aria-pressedand its own tab stop. The toggle doesn't use the ARIA radio group pattern, which has a single tab stop and arrow-key navigation. - Transitions are turned off when
prefers-reduced-motion: reduceis set. - If the plugin's JavaScript doesn't run, Material's native palette switch is left visible and fully functional — nothing is hidden until this plugin's own script confirms it found both radios and successfully built its replacement.
Analytics
Listen for the light-dark:modechange event on document to record scheme changes.
event.detail contains mode ("light" or "dark") and previousMode:
document.addEventListener("light-dark:modechange", (event) => {
const { mode, previousMode } = event.detail;
gtag("event", "color_scheme_change", { mode, previous_mode: previousMode });
});
The event fires only on a click that changes the scheme — not on page load, and not when clicking
the option that's already active. To read the scheme at any time, including on page load, use
Material's own data-md-color-scheme attribute on <body>:
const scheme = document.body.getAttribute("data-md-color-scheme"); // "default" or "slate"
Testing
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
playwright install chromium
pytest
tests/fixture_site/ is a small Material for MkDocs site that uses the plugin. The tests build it
once, serve it locally, and run Playwright against it:
test_behavior.py: replacing the native form, switching scheme, persistence, toast, events.test_accessibility.py: axe-core checks.test_keyboard.py: keyboard use and focus.
axe-core is included in tests/vendor/, so the tests don't need network access.
License
Metadata
Release files for mkdocs-light-dark-toggle 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mkdocs_light_dark_toggle-0.1.0.tar.gz | 161.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mkdocs_light_dark_toggle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 172.6 kB
Release files / mkdocs_light_dark_toggle-0.1.0.tar.gz
| Download URL | mkdocs_light_dark_toggle-0.1.0.tar.gz |
|---|---|
| Size | 161.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df80c3db3b4cbf6cd98e77979a30817ad10108da5745dcee0db7359ab0155df5
|
|
BLAKE2b-256 checksum How to use checksums |
f9778c58e8ea49e8acde1eebea618ca09fe2a1afb87fec87f54e55cefd369823
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / mkdocs_light_dark_toggle-0.1.0-py3-none-any.whl
| Download URL | mkdocs_light_dark_toggle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9e9aab683f63254dbd6fa322807c55d51e382767e7f711c69cf1391b4f84e03a
|
|
BLAKE2b-256 checksum How to use checksums |
951f45c88118b1d810d3607234f5c251b400c8f73367e1a697d6703c4d386b16
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log