Edit: I managed to get it working by using :has and nesting css classes!

body:has(#theme-toggle:checked) {
  background-color: #eff1f5;
  .content {
    color: #4c4f69;
  }
  .header {
    color: #8839ef
  }
  .nav {
    background-color: #dce0e8;
    color: #4c4f69;
  }
}

I’m making a website for my school’s robotics team and I’m trying to create a dark theme toggle but it’s just not working. I’m trying to avoid javascript and I’ve seen this kind of thing done with only css and html before so I know it’s possible. any advice?

repo: https://github.com/WrenHavoc/JudgeMent-Call-Website

edit: currently my code looks something like this:

#theme-toggle:checked ~ body {
  background-color: #eff1f5;
  color: #fff;
}

#theme-toggle:checked ~ html {
  background-color: #eff1f5;
}

#theme-toggle:checked ~ .content {
  background-color: #eff1f5;
}

the button itself is a checkbox that has display set to none and the label set as an svg so when you click the icon, it gets checked.

<input style="display: none;" type="checkbox" id="theme-toggle">
                <label for="theme-toggle" class="theme-button">
                    <img class="theme-button-svg" src="./icons/half-moon.svg">
                </label>

I used a similar strategy when making the menu for the site so I know it should work

.menu {
  position:absolute;
  margin:0%;
  right:20px;
  top:20px;
}

.menu-button {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 30px;
  height: 22px;
  cursor: pointer;
  z-index: 2; /* above menu */
}

.menu-button span {
  display: block;
  height: 4px;
  background-color: #cba6f7;
  border-radius: 2px;
  transition: all 0.3s ease;
}

.menu-items {
  top: 30px;
  right: -20px;
  width: 200px;
  background-color: #181825;
  position: absolute;
  display: none;
}

.menu-items li {
  margin: 20px 0;
}

.menu-items a {
  text-decoration: none;
  color: #cba6f7;
  font-size: 18px;
  padding:5px;
}

.menu-items a:hover {
  text-decoration: none;
  background-color: #cba6f7;
  color: #181825;
  font-size: 18px;
}

.menu-selected {
  text-decoration: underline;
  text-decoration-color: #cdd6f4;
  text-decoration-thickness: 3px;
}

.menu-selected:hover {
  text-decoration-color: #181825;
}

#menu:checked ~ .menu-items {
  display: inline;
}

#menu:checked + .menu-button span:nth-child(1) {
  transform: rotate(45deg) translate(5px, 7.5px);
}
#menu:checked + .menu-button span:nth-child(2) {
  opacity: 0;
}
#menu:checked + .menu-button span:nth-child(3) {
  transform: rotate(-45deg) translate(5px, -7.5px);
}
<input style="display: none;" type="checkbox" id="menu">
                <label for="menu" class="menu-button">
                    <span></span>
                    <span></span>
                    <span></span>
                </label>
  • Kissaki@programming.dev
    link
    fedilink
    English
    arrow-up
    1
    ·
    2 days ago

    aside: A simple head meta tag with color-scheme light dark will make the web-browser respect user settings and show light or dark. No need for a toggle the user has to activately activate. One meta tag is enough.

    <meta name="color-scheme" content="light dark" />

    MDN meta color-scheme


    If you override the default colors, the light-dark() CSS function is very useful, if the “newly available” compatibility is enough.

    html { background-color: light-dark(#fff, #222); }
    

    or with variables for reuse and centralized definitions :root { --bg-0: light-dark(#fff, #222); } html { background-color: var(--bg-0); }

    Well, I only wanted to suggest the meta alternative and went on a longer tangent. I want to see more websites with dark scheme, especially given how easy it is to enable, and how straight-forward it is if you know how to get started even with custom coloring.

  • e0qdk@reddthat.com
    link
    fedilink
    arrow-up
    3
    ·
    3 days ago

    As others have mentioned, the :has() approach is cleaner, but if you need to get this to work on older browsers (i.e. that don’t support Baseline 2023 yet), you can use the ~ approach by moving the checkbox up to the top of body and putting all the body content into a div that follows it.

    Basically, something like:

    <body>
        <input style="display:none" type="checkbox" id="hidden-checkbox"></input>
        <div id="main-content">
            <label for="hidden-checkbox">
               ...
    

    That way you can use #hidden-checkbox:checked ~ #main-content (followed by additional selectors) to modify children of #main-content only if the checkbox is checked.

    Note that you still need a tiny bit of JS to make the choice persistent between page loads even with CSS checkbox toggle tricks.

  • Victor@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    4 days ago
    #theme-toggle:checked ~ body {
      background-color: #eff1f5;
      color: #fff;
    }
    
    #theme-toggle:checked ~ html {
      background-color: #eff1f5;
    }
    
    #theme-toggle:checked ~ .content {
      background-color: #eff1f5;
    }
    

    I don’t think this will work. Read up on what the “subsequent sibling” selector really does and double check that it’s what you really want here.

    Also check out the color-scheme property and the articles listed under the See also heading.

    https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/color-scheme#see_also

    Maybe that can give you some ideas where you need to brush up on modern dark mode styling. 👍

    • Wren@lemmy.dbzer0.comOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      4 days ago

      I got a piece of code from c/programming that works Using body:has(#theme-toggle:checked)

      I prefer doing it that way because I use a browser with anti-fingerprinting which hides the preferred theme so prefers-color-scheme doesn’t work as well

      • Victor@lemmy.world
        link
        fedilink
        arrow-up
        2
        ·
        4 days ago

        Check this simple example out:

        body {
          color-scheme: light dark;
        }
        
        p {
          color: light-dark(black, white);
          background-color: light-dark(white, black);
        }
        

        You don’t need any fancy selectors with this method. Will this not work in your browser due to the anti-fingerprinting feature?

        I will say, avoiding JavaScript completely will make the value reset after each reload, so might not be very useful perhaps, depending on your needs. You could store the value in localStorage, perhaps.

        • Ephera@lemmy.ml
          link
          fedilink
          English
          arrow-up
          2
          ·
          4 days ago

          I haven’t tried it, but I’m guessing to implement a manual toggle with color-scheme, you could then do this:

          body {
              color-scheme: light dark;
          }
          body:has(#theme-toggle:checked) {
              color-scheme: dark;
          }
          
          • Victor@lemmy.world
            link
            fedilink
            arrow-up
            2
            ·
            edit-2
            3 days ago

            Indeed you can, just like this (see below). I just tried it in a codepen and it seems to work. Although do note that if you put light dark as the default value, once you toggle the switch off again, it’ll choose whatever mode the user agent wants, which might still be dark mode. So if you want it to be light by default, you’ll need to use a value that enforces that.

            body {
              color: light-dark(black, white);
              background-color: light-dark(white, black);
            }
              
            body:has(#theme-toggle:checked) {
              color-scheme: dark;
            }
            
            • Ephera@lemmy.ml
              link
              fedilink
              English
              arrow-up
              2
              ·
              edit-2
              4 days ago

              Ah yeah, I guess, users would expect some action to happen when they click that toggle, not just for it to change from automatic-dark to manual-dark.

              Perhaps the simplest non-JS and non-persistent solution would then be to have it pick the color-scheme automatically by default, but if the checkbox is checked, then set the colors to the opposite.

              So, probably something like this:

              body {
                color-scheme: light dark;
              
                color: light-dark(black, white);
                background-color: light-dark(white, black);
              }
              @media (prefers-color-scheme: dark) {
                body:has(#theme-toggle:checked) {
                  color-scheme: light; /*opposite*/
                }
              }
              @media (prefers-color-scheme: light) {
                body:has(#theme-toggle:checked) {
                  color-scheme: dark; /*opposite*/
                }
              }
              

              You could probably even theme the checkbox to show a sun or a moon, depending on the current color scheme. 🙃

              • Victor@lemmy.world
                link
                fedilink
                arrow-up
                2
                ·
                3 days ago

                All true. 👍

                Although OP mentioned they can’t use prefers-color-scheme for fingerprinting reasons. And for some reason are against a little bit of JavaScript to help save the value.

                A checkbox also can’t offer the three common values: Light, Dark, and Follow system. But oh well, to each their own.

                • Ephera@lemmy.ml
                  link
                  fedilink
                  English
                  arrow-up
                  2
                  ·
                  3 days ago

                  Well, to me, it sounded like they themselves can’t rely on prefers-color-scheme, which is why the manual toggle is necessary, but it doesn’t hurt to support it for other folks.

                  I guess, that does mean that a solution without persistence is going to be annoying, but yeah, I don’t think that’s solvable without JS. One could ensure that the JS is entirely optional, so that when the user blocks it, you simply get no persistence, but that’s probably about it…

    • sznowicki@lemmy.world
      link
      fedilink
      arrow-up
      1
      ·
      4 days ago

      I think this could work with css variables. This way you can define it in root and then some sibling selector could switch the value. That way you can use the variable anywhere in code.

      • Victor@lemmy.world
        link
        fedilink
        arrow-up
        1
        ·
        edit-2
        3 days ago

        CSS variables yes, that’s fine. But using this selector in this way, especially with body and html as the “subsequent sibling”, won’t work (well). The body and html elements can’t be siblings of a checked element.