{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "Over at the [Design Systems House website](https://ds.house), I made a small toggle for light and dark mode. While there’s an abundance of posts describing this, I didn’t find any that made it into a compact script of a few lines. Here’s how I did it, and some additional considerations to keep in mind.\n\n## The script\n\nThis is all the JavaScript that makes the toggle work:\n\n```\nconst LOCALSTORAGE_KEY = 'dark';\nconst $toggle = document.getElementById('toggle');\n\nconst { matches } = window.matchMedia('(prefers-color-scheme: dark)');\nconst stored = window.localStorage.getItem(LOCALSTORAGE_KEY);\n$toggle.checked = stored !== null ? stored === 'true' : matches;\n\n$toggle?.addEventListener('change', (ev) => {\n window.localStorage.setItem(LOCALSTORAGE_KEY, String(ev.target.checked));\n});\n```\n\nThis assumes that there is a `<input type=\"checkbox\" id=\"toggle\">` element in the HTML. Let’s go over the lines:\n\n1. We define a constant for the `localStorage` key.\n2. We get the toggle element from the DOM.\n3. We check if the user has a preference for dark mode using `matchMedia`.\n4. We check if there’s a stored value in `localStorage`\n5. We set the toggle’s checked state based on the stored value or the user’s preference.\n6. We add an event listener to the toggle.\n7. When the toggle changes, we store the boolean value as a string in `localStorage`.\n\nImportantly, we assume that the initial presentation is “light” and explicitly look for if there’s any setting for “dark”. The first thing we look at is if it was set as “dark” for this site using the toggle from a previous session. If not, we check what the OS preference is. This is setting the initial state of the toggle element, being checked if “dark” is picked up from either of those conditions. Then we listen for changes to the toggle and only update the `localStorage` value when the toggle is changed. This overrides the OS user preference when set.\n\n## The styles\n\nNow with the [baseline `:has()` selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:has), getting this to behave is pretty easy with a single selector to introduce a new expression:\n\n```\nbody:has(#toggle:checked) {\n /* dark mode styles */\n}\n```\n\nThis is a very simple way to apply styles based on the state of the toggle. The `:has()` selector allows us to check if the toggle is checked and apply styles accordingly. This way, we don’t need to add any additional classes or attributes to the body element to indicate the current mode. We can just rely on the state of the toggle itself.\n\nI’m deliberately avoiding the HTML of the toggle here. If you really want to know what the HTML is, you can grab it from [the source over at ds.house](view-source:https://ds.house/). I think the JavaScript and CSS used are more important for this article.\n\n## About system settings\n\nWhen I initially made this kind of toggle for light/dark, I thought about it differently. Instead of light/dark, I wanted to toggle between “system” and “opposite of system”. In other words, if the OS was set as “dark”, the toggle would then either align to what was set in the system or be the opposite when toggled. Visually in terms of presentation, there would be no difference. If your system was set to “dark”, then by default the site would be dark. If your system was “dark” and you wanted this site to be “light”, you would switch to the “opposite of system” option.\n\nThis helps avoid the problem of setting a specific flag on the site once set. In the implementation above and at ds.house, once we store the state expected by the user for the website, there’s no way of resetting it back to the state of not being set. It’s either “light” or “dark” (or `false`/`true` in the code). With the “system” and “opposite of system” approach, you can always reset to “system” to align with the OS preference. In this way, setting `false` is the same as not setting anything. This is a nice way to allow users to easily reset their preference without needing to clear `localStorage` or have a separate “reset” button.\n\nThere’s a few problems with this, one of them being that I could never figure out a good way to explain this setting. “System” as the default is fairly straightforward, but “opposite of system” is a bit more difficult to understand. The other problem is that it doesn’t really align with the mental model of how users think about light/dark mode. They think in terms of light and dark, not “system” and “opposite of system”. So I went with the more straightforward approach of just toggling between light and dark.\n\nIf you’d rather use a `<select>` instead of a `<input type=\"checkbox\">`, you can modify the script to accommodate that by referencing `.selected` instead of `.checked` in the appropriate places. Just make sure your HTML has matching values for `localStorage` and the options in your `<select>`. You might want this if you want to expose “system”, “light”, and “dark” as options. [Kilian Valkhof recommends](https://kilianvalkhof.com/2020/design/your-dark-mode-toggle-is-broken/) having all three and [Bramus Van Damme explains](https://www.bram.us/2022/05/25/dark-mode-toggles-should-be-a-browser-feature/#the-problem-with-dark-mode-toggles) the different possible levels of setting this information.\n\n## Knowing your audience\n\nIf you’re wondering why I opted to *not* expose the “light”, “dark”, and “system” options at ds.house, it’s because no person is spending a signficant amount of time on that website. Enough that they’ll visit everyday to be productive. Setting whether or not “light” or “dark” is right for this person is most likely a one-time thing specifically for a single visit. It is unlikely that a user will want the presentation of this site to change over the course of a day, unlike productivity applications like email clients where you’ll visit multiple times a day. Those experiences will expect smarter customization options.\n\nFor a small website that gets a visit every once in a while, keeping it simple might be the better approach. But it’s important to know the tradeoffs. And if you ever come up with a good word for “opposite of system”, do let me know.",
"version": "1.0"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreieh4e44b3vebbkjgioz67dfssixpucjprxd2fh6csu4plzefdc3tq"
},
"mimeType": "image/png",
"size": 177492
},
"description": "Switching between light and dark as lazily as possible.",
"path": "/posts/tiny-toggle",
"publishedAt": "2026-05-05T00:00:00.000Z",
"site": "https://blog.damato.design",
"tags": [
"css",
"color",
"tokens"
],
"textContent": "Over at the Design Systems House website, I made a small toggle for light and dark mode. While there’s an abundance of posts describing this, I didn’t find any that made it into a compact script of a few lines. Here’s how I did it, and some additional considerations to keep in mind.\n\nThe script\n\nThis is all the JavaScript that makes the toggle work:\n\nThis assumes that there is a element in the HTML. Let’s go over the lines:\nWe define a constant for the key.\nWe get the toggle element from the DOM.\nWe check if the user has a preference for dark mode using .\nWe check if there’s a stored value in \nWe set the toggle’s checked state based on the stored value or the user’s preference.\nWe add an event listener to the toggle.\nWhen the toggle changes, we store the boolean value as a string in .\n\nImportantly, we assume that the initial presentation is “light” and explicitly look for if there’s any setting for “dark”. The first thing we look at is if it was set as “dark” for this site using the toggle from a previous session. If not, we check what the OS preference is. This is setting the initial state of the toggle element, being checked if “dark” is picked up from either of those conditions. Then we listen for changes to the toggle and only update the value when the toggle is changed. This overrides the OS user preference when set.\n\nThe styles\n\nNow with the baseline selector, getting this to behave is pretty easy with a single selector to introduce a new expression:\n\nThis is a very simple way to apply styles based on the state of the toggle. The selector allows us to check if the toggle is checked and apply styles accordingly. This way, we don’t need to add any additional classes or attributes to the body element to indicate the current mode. We can just rely on the state of the toggle itself.\n\nI’m deliberately avoiding the HTML of the toggle here. If you really want to know what the HTML is, you can grab it from the source over at ds.house. I think the JavaScript and CSS used are more important for this article.\n\nAbout system settings\n\nWhen I initially made this kind of toggle for light/dark, I thought about it differently. Instead of light/dark, I wanted to toggle between “system” and “opposite of system”. In other words, if the OS was set as “dark”, the toggle would then either align to what was set in the system or be the opposite when toggled. Visually in terms of presentation, there would be no difference. If your system was set to “dark”, then by default the site would be dark. If your system was “dark” and you wanted this site to be “light”, you would switch to the “opposite of system” option.\n\nThis helps avoid the problem of setting a specific flag on the site once set. In the implementation above and at ds.house, once we store the state expected by the user for the website, there’s no way of resetting it back to the state of not being set. It’s either “light” or “dark” (or / in the code). With the “system” and “opposite of system” approach, you can always reset to “system” to align with the OS preference. In this way, setting is the same as not setting anything. This is a nice way to allow users to easily reset their preference without needing to clear or have a separate “reset” button.\n\nThere’s a few problems with this, one of them being that I could never figure out a good way to explain this setting. “System” as the default is fairly straightforward, but “opposite of system” is a bit more difficult to understand. The other problem is that it doesn’t really align with the mental model of how users think about light/dark mode. They think in terms of light and dark, not “system” and “opposite of system”. So I went with the more straightforward approach of just toggling between light and dark.\n\nIf you’d rather use a instead of a , you can modify the script to accommodate that by referencing instead of in the appropriate places. Just make sure your HTML has matching values for and the options in your . You might want this if you want to expose “system”, “light”, and “dark” as options. Kilian Valkhof recommends having all three and Bramus Van Damme explains the different possible levels of setting this information.\n\nKnowing your audience\n\nIf you’re wondering why I opted to not expose the “light”, “dark”, and “system” options at ds.house, it’s because no person is spending a signficant amount of time on that website. Enough that they’ll visit everyday to be productive. Setting whether or not “light” or “dark” is right for this person is most likely a one-time thing specifically for a single visit. It is unlikely that a user will want the presentation of this site to change over the course of a day, unlike productivity applications like email clients where you’ll visit multiple times a day. Those experiences will expect smarter customization options.\n\nFor a small website that gets a visit every once in a while, keeping it simple might be the better approach. But it’s important to know the tradeoffs. And if you ever come up with a good word for “opposite of system”, do let me know.",
"title": "Tiny toggle",
"updatedAt": "2026-06-13T19:13:27.600Z"
}