Dark Mode Toggle without Javascript

Written by Lucie Zdeňková on 2025-01-02

htmlcss

The following code snippets of HTML and CSS demonstrates a simple setup to enable dark mode using only HTML and CSS, without any JavaScript. This solution takes user preferences into account and allows the user to manually toggle between light and dark mode. Practical example can be seen in the header of this page.


UI of dark mode toggle

HTML code

The HTML code structure relies on a hidden checkbox as the toggle, paired with a label styled to visually represent the dark mode switch. To ensure correct behaviour it's necessary to have <input> element on the same level as a wrapper <div> element with class="backgroud". Element <label> represents the UI of the toogle and can be placed anywhere in the <div class="background> element.

<body>
    <input type="checkbox" id="darkmode-toggle" />
    <div class="background">
        <!-- Replace this section with your desired HTML content -->
        <label for="darkmode-toggle" class="darkmode-label">
            <span class="sun"></span>
            <span class="moon"></span>
        </label>
        <!-- Replace this section with your desired HTML content -->
    </div>
</body>

CSS variables

Lets's get into CSS part. Key feature for creating dark mode function is setting so called CSS variables. Every property that requires dynamic behaviour based on color preference has to be defined here. We need to start with the selector :root that represents the default light mode. Variables --bg: white; and --text: black; represents change of the visual background and the text color on the website. Rest of the variables --toggle-bg, --gradient-bg1, --gradient-bg2, --label-left, --transformantion, --img-sun, --img-moon defines changes of toggle UI itself.

:root {
    --bg: white;
    --text: black;
    --toggle-bg: #ebebeb;
    --gradient-bg1: #ffcc89;
    --gradient-bg2: #d8860b;
    --label-left: 2px;
    --img-sun: url('img/sun-white.svg');
    --img-moon: url('img/moon.svg');
}

Next comes definition of colors for user's default dark mode using selector @media (prefers-color-scheme: dark). As you can see, variables --bg, --text have switched values, the rest of the variables for the toggle UI has altered values for dark visual.

@media (prefers-color-scheme: dark) {
    :root {
        --bg: black;
        --text: white;
        --toggle-bg: #242424;
        --gradient-bg1: #777;
        --gradient-bg2: #3a3a3a;
        --label-left: 98px;
        --transformantion: translateX(-100%);
        --img-sun: url('img/sun.svg');
        --img-moon: url('img/moon-white.svg');
    }
}

Third selector #darkmode-toggle:checked~* represents behaviour when the dark mode is toggled manually by the user on the website. It is sufficient to simply copy and paste the variables with it's values from the previous step.

#darkmode-toggle:checked~* {
    --bg: black;
    --text: white;
    --toggle-bg: #242424;
    --gradient-bg1: #777;
    --gradient-bg2: #3a3a3a;
    --label-left: 98px;
    --transformantion: translateX(-100%);
    --img-sun: url('img/sun.svg');
    --img-moon: url('img/moon-white.svg');
}

Lastly, behaviour for switching from dark mode to light mode has to be defined using selector @media (prefers-color-scheme: dark) { #darkmode-toggle:checked~* {. Because this represents light mode, we can simply copy and paste all variables from the very first step. It's important and unevitable to use all those four selectors in this exact order, due to CSS overriding rules.

@media (prefers-color-scheme: dark) {
    #darkmode-toggle:checked~* {
        --bg: white;
        --text: black;
        --toggle-bg: #ebebeb;
        --gradient-bg1: #ffcc89;
        --gradient-bg2: #d8860b;
        --label-left: 2px;
        --img-sun: url('img/sun-white.svg');
        --img-moon: url('img/moon.svg');
    }
}

Illustrations

This toggle user interface contains two illustrations of sun and moon in svg format. In order to change their color with toggle state, they are implemented as backgroud svg images of <span class="sun"> and <span class="moon"> element. Those illustrations were downloaded from UXWING. In total, four illustrations are needed, two black ones and two white ones. Save them to your /img directory.

Styling div wrapper

The .background class styles the main container of the webpage and ensures that website adapts to the selected theme using properties from variables in the previous code snippet. Properties backgroud: var(--bg); and color: var(--text); are using previously defined colors in variables. min-height: 100vh; ensures that the container always occupies at least the full height of the viewport (100% of the visible browser window), in order to extend backgroud color across the entire page, even if the content is minimal. display: flex; activates a flexbox layout for the container. This makes it easy to align and distribute child elements within the container. flex-direction: column; aligns child elements vertically (in a column) from top to bottom. This is useful for stacking content like headers, main sections, and footers.

.background {
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

This blogpost outlined how to define CSS variables for dark mode and how to style the wrapper div container. In the next part, we will demonstrate how to style the dark mode toggle itself using our defined variables.

Thank you for reading and see ya in the second part!

Resources:

  1. An HTML and CSS only dark-mode toggle button