Skip to main content

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:

Light/dark toggle in light mode, sun option active

Dark mode, moon active:

Light/dark toggle in dark mode, moon option 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-pressed and 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: reduce is 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

MIT

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)

Source distribution for mkdocs-light-dark-toggle 0.1.0
File Size Uploaded
mkdocs_light_dark_toggle-0.1.0.tar.gz 161.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mkdocs-light-dark-toggle 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page