CSS Day Night Switch (CSS/HTML only) [PART 2]

CSS Day Night Switch (CSS/HTML only) [PART 2]

Now let's get this finished by implementing the night state of our nice day-night-switch by implementing a moon, day-night transitions and a few sparkling stars as a bonus.

Read the full article or watch me code this on Youtube:

Result

Turning off the lights

So, let's start by turning off the lights, which means that as soon as the switch is unchecked, the background color as well as the border color fade to a darker tone of blue.

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    border-color: #2a4569;
    background-color: #223349;
  }
}

Since also everything appears darker at night (who would have thought...), the mountains also fade from a lighter gray to a darker one, as soon as the checkbox is unchecked.

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    > .mountains {
      > * {
        background-color: #878787;
        border-color: #5c5c5c;
      }
    }
  }
}

Sunset

First let's define a position to which sun and moon are shifted as soon as they need to set. In this case sun and moon are going to shifted 25% of the height below the lower edge, i.e. 1.25 x the height of the entire switch.

input.day-night-switch {
  --shift: calc(var(--height) * 1.25);
}

Now, to make the sun set, we simply update its value for the top property as soon as the checkbox is unchecked. Additionally it is scaled down to 0 to create a little bit of a stronger sunset effect.

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    > .celestial {
      &.sun {
        transition-delay: 0ms;
        top: var(--shift);
        left: var(--pos-right);
        transform: scale(0);
      }
    }
  }
}

Let the moon come out!

Now that the sun is able to come and go, there's space for the moon. Therefore let's put a div with the celestial class for the shared styling of sun and moon and the moon class for the moon-specific styling...

label(for="day-night").day-night-switch
  ...
  div.celestial.moon

... which is most importantly the moon's color:

label.day-night-switch {
  > .celestial {
    &.moon {
      background-color: #d2cec4;
      border-color: #a9a18f;
    }
  }
}

Analogously to the sun, the moon gets also set its coordinates to where it should be during daytime (checked) and nighttime (unchecked):

input.day-night-switch {
  // checked styles
  &:checked {
    + label.day-night-switch {
      > .celestial {
        &.moon {
          transition-delay: 0ms;
          left: var(--pos-left);
          top: var(--shift);
          transform: scale(0);
        }
      }
    }
  }
}

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    > .celestial {
      &.moon {
        transition-delay: var(--transition-duration);
        top: var(--padding);
        left: var(--pos-left);
        overflow: hidden;
        transform: scale(1);
      }
    }
  }
}

Craters!

However, the moon looks still a little but dull and boring and actually not really like a moon. To fix this, let's add a few craters:

label(for="day-night").day-night-switch
  ...
  div.celestial.moon
    div.craters
      div.crater
      div.crater
      div.crater
      div.crater
      div.crater

Very similar to the clouds the craters are positioned relatively to the --switch-size inside the moon. Each crater is in the end just a div which is made a circle through border-radius: 50%.

The border width of each crater is set to to slightly smaller value than the border width of the moon. But it should never be smaller than 1px, which is achieved by using the max(...) function, returning always the largest value of the ones which are passed to it. So should 65% of the --border-width be ever the smaller one, 1px is returned.

label.day-night-switch {
  > .celestial {
    &.moon {
      > .craters {
        > .crater {
          background-color: #d2cec4;
          border-color: #a9a18f;
          border-width: calc(max(var(--border-width) * 0.65, 1px));
          border-style: solid;
          position: absolute;

          &:nth-child(1) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.15);
            height: calc(var(--switch-size) * 0.15);
            top: calc(var(--switch-size) * 0.7);
            left: calc(var(--switch-size) * 0.4);
          }

          &:nth-child(2) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.3);
            height: calc(var(--switch-size) * 0.3);
            top: calc(var(--switch-size) * 0.1);
            left: calc(var(--switch-size) * -0.05);
          }

          &:nth-child(3) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.1);
            height: calc(var(--switch-size) * 0.1);
            top: calc(var(--switch-size) * 0.2);
            left: calc(var(--switch-size) * 0.6);
          }

          &:nth-child(4) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.1);
            height: calc(var(--switch-size) * 0.1);
            top: calc(var(--switch-size) * 0.3);
            left: calc(var(--switch-size) * 0.25);
          }

          &:nth-child(5) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.2);
            height: calc(var(--switch-size) * 0.2);
            top: calc(var(--switch-size) * 0.5);
            left: calc(var(--switch-size) * 0.8);
          }
        }
      }
    }
  }
}

Sparkling stars

Now finally, let's make this night sky perfectly clear by adding a few sparking stars. The good thing is that we already have a few .decoration elements for the clouds in the DOM that we don't need at night time. So let's simply reuse them for the stars and by that creat

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    > .decorations {
      > .decoration {
        position: absolute;
        background-color: white;
        border-radius: 50%;
        width: calc(max(var(--border-width) * 0.75, 2px));
        height: calc(max(var(--border-width) * 0.75, 2px));
        animation: 2s sparkle ease-in-out infinite;
        animation-direction: alternate;

        &:nth-child(1) {
          top: calc(var(--switch-size) * 0.7);
          left: calc(var(--switch-size) * 1.7);
        }
        &:nth-child(2) {
          animation-delay: 300ms;
          animation-duration: 3s;
          top: calc(var(--switch-size) * 0.4);
          left: calc(var(--switch-size) * 1.4);
        }
        &:nth-child(3) {
          animation-delay: 800ms;
          animation-duration: 3.5s;
          top: calc(var(--switch-size) * 0.9);
          left: calc(var(--switch-size) * 2.2);
        }
        &:nth-child(4) {
          animation-delay: 1400ms;
          animation-duration: 2.5s;
          top: calc(var(--switch-size) * 0.3);
          left: calc(var(--switch-size) * 2);
        }
      }
    }
  }
}

The sparkling animation itself is just going to alternate between 100% and 25% of opacity for each star:

@keyframes sparkle {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0.25;
  }
}

input.day-night-switch {
  // unchecked styles
  + label.day-night-switch {
    > .decorations {
      > .decoration {
        transition: all var(--transition-duration) ease-in-out;
        animation: 2s sparkle ease-in-out infinite;
        animation-direction: alternate;
      }
    }
  }
}

To make the sparkling a little bit more exciting, different delays and duration are applied to each star...aaaand it's done!

// unchecked styles
  + label.day-night-switch {
    > .decorations {
      > .decoration {

        &:nth-child(2) {
          animation-delay: 300ms;
          animation-duration: 3s;
        }
        &:nth-child(3) {
          animation-delay: 800ms;
          animation-duration: 3.5s;
        }
        &:nth-child(4) {
          animation-delay: 1400ms;
          animation-duration: 2.5s;
        }
      }
    }

That's all you need to create such a nice little day night swith. I hope you enjoyed creating it.