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

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

ยท

9 min read

See also Part 2

Today let's build a nice little day night switch with sunset and sunrise and a lot of CSS transitions. In this first part we're going to take care of the day phase of the switch, so we start with a checkbox in checked state.

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

Result

Basic Markup

The first thing we need is a checkbox and a label. The most important part here is that the ID of the input matches the value of the for attribute of the label, because the checkbox will not be visible and all of the styled content is going to be inside the label. So when the label is clicked, the checkbox needs to be triggered, which is why the for value needs to match the ID of the checkbox.

input(type="checkbox",checked).day-night-switch#day-night
label(for="day-night").day-night-switch
input.day-night-switch {
  display: none;
}

Basic Styling

Let's define a few variables which make resizing much easier. The --size CSS variable is what every other dimension is derived from, so the switch can be resized to whatever size you want it to be.

To create the shape of the switch it's going to have half of the width as the height and the border-radius is set to be the same as the height to give it the typical switchy shape.

Since we're going to use the transition duration a lot, it's wrapped up in variable called --transition-duration.

The actual size of the moving part of the switch is calculated to fit nicely into the shape of the container. Because a few calculations are done relatively to the switch size (scalability), it's put into a variable called --switch-size.

label.day-night-switch {
  --size: 400px;
  --height: calc(var(--size) / 2);
  --padding: calc(var(--size) * 0.04); //8px;
  --border-width: calc(var(--size) * 0.02); //4px;
  --transition-duration: 250ms;
  --switch-size: calc(
    var(--height) - 2 * var(--padding) - 2 * var(--border-width)
  );

  width: var(--size);
  height: var(--height);
  //background: rgba(white, 0.2);
  border-radius: var(--height);
  border: var(--border-width) solid white;
  position: relative;
  transition: all var(--transition-duration) ease-in-out;
  cursor: pointer;
}

Let the sun come out!

The sun is just a plain div.celestial.sun:

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

Since the sun will be put on the right, let's calculate its position relative to the --switch-size and use it for later reference. Both moon and sun share the same basic styling, so the class .celestial sets up the dimensions, border radius positioning mode. To make the sun actually look like a sun, it's getting a nice yellow color with a slightly darker border.

label.day-night-switch {
  --pos-right: calc(
    var(--size) - var(--switch-size) - var(--padding) - 2 * var(--border-width)
  );

  > .celestial {
    transition: all var(--transition-duration) ease-in-out;
    width: var(--switch-size);
    height: var(--switch-size);
    border: var(--border-width) solid green;
    position: absolute;
    border-radius: var(--switch-size);
    background: white;

    &.sun {
      background-color: #fdc82e;
      border-color: #e3ad0d;
    }
  }
}

The day state is given through the checked state and therefore let's group all styles that are important for it together. So, the position on the top right of the switch is given and during day the sky is bright, which is why the switch also has a bright blue color.

input.day-night-switch {
  // checked styles
  &:checked {
    + label.day-night-switch {
      border-color: #3190bf;
      background-color: #6cbde5;

      > .celestial {
        &.sun {
          transition-delay: var(--transition-duration);
          left: var(--pos-right);
          top: var(--padding);
          transform: scale(1);
        }
    }
  }
}

A beautiful day in the mountains

Let's not just have a switch, but also a beautiful scenery, so we're adding two divs for the mountains.

  • markup: mountains
    label(for="day-night").day-night-switch
    ...
    div.mountains
      div
      div
    

The mountains are basically just rotated by 45 degrees and positioned in a way such that only around one half of them is visible. Additionally the cap is slightly rounded off to make it look less edgy.

In order to maintain scalability all dimensions are calculated relatively to the switch size.

label.day-night-switch {
 > .mountains {
    display: inline-block;
    position: absolute;
    top: calc(var(--switch-size) * 0.85);
    left: calc(var(--switch-size) * 0.7);

    > * {
      position: absolute;
      display: inline-block;
      border-width: var(--border-width);
      border-style: solid;
      transform: rotate(45deg);
      transition: var(--transition-duration) all ease-in-out;
      border-top-left-radius: calc(var(--switch-size) * 0.1);
      background-color: white;
      border-color: black;

      &:nth-child(1) {
        width: calc(var(--switch-size) * 0.9);
        height: calc(var(--switch-size) * 0.9);
        top: calc(var(--switch-size) * 0.1);
        left: 0;
      }

      &:nth-child(2) {
        width: calc(var(--switch-size) * 0.45);
        height: calc(var(--switch-size) * 0.45);
        top: calc(var(--switch-size) * 0.2);
        left: calc(var(--switch-size) * 0.6);
      }
    }
  }
}

input.day-night-switch {
  // checked styles
  &:checked {
    + label.day-night-switch {
      > .mountains {
        > * {
          background-color: #d4d4d4;
          border-color: #a8a8a8;
        }
      }
    }
  }
}

A vibing cloud

To make the left side of the switch look less empty, let's add a cloud. It consists of four divs, simply being rounded off and positioned together to create a nice little "good weather cloud".

label(for="day-night").day-night-switch
  ...
  div.decorations
    div.decoration
    div.decoration
    div.decoration
    div.decoration

Just like the mountains they are dimensionsed and positioned absolutely relative (pun intended) to the --switch-size. If you may wonder why they also need transitions, continue reading part 2 ๐Ÿ˜„.

label.day-night-switch {
  > .decorations {
    > .decoration {
      transition: all var(--transition-duration) ease-in-out;
      position: absolute;
    }
  }
}

input.day-night-switch {
  // checked styles
  &:checked {
    + 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));

          &:nth-child(1) {
            border-radius: calc(var(--switch-size) * 0.3);
            width: calc(var(--switch-size) * 0.85);
            height: calc(var(--switch-size) * 0.3);
            top: calc(var(--switch-size) * 0.6);
            left: calc(var(--switch-size) * 0.45);
          }
          &:nth-child(2) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.35);
            height: calc(var(--switch-size) * 0.35);
            top: calc(var(--switch-size) * 0.5);
            left: calc(var(--switch-size) * 0.35);
          }
          &:nth-child(3) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.3);
            height: calc(var(--switch-size) * 0.3);
            top: calc(var(--switch-size) * 0.5);
            left: calc(var(--switch-size) * 0.85);
          }
          &:nth-child(4) {
            border-radius: 50%;
            width: calc(var(--switch-size) * 0.4);
            height: calc(var(--switch-size) * 0.4);
            top: calc(var(--switch-size) * 0.4);
            left: calc(var(--switch-size) * 0.55);
          }
        }
      }
    }
  }
}

Now, let's make the clouds extra playful, by adding a slightly pulsating/vibing animation to each part of it. Therefore the animation is setup to scale between 1.0 and 1.2.

  • clouds: vibing

    @keyframes vibe {
    0% {
      transform: scale(1);
    }
    
    100% {
      transform: scale(1.2);
    }
    }
    

    The animation is set for each individual part of the cloud and by adding some animation delays and variation in duration, it looks like proper cozy vibing cloud. Most importantly animation-direction: alternate; will make the animation first play in one direction and then again backwards, so it constanlty alternates slowly between a scale of 1.0 and 1.2.

input.day-night-switch {
  // checked styles
  &:checked {
    + label.day-night-switch {
      > .decorations {
        > .decoration {
          animation: 4s vibe ease-in-out infinite;
          animation-direction: alternate;

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

So, that's what's needed for the day phase. Now let's head right over to the night phase and read on in Part 2.