CSS only toggle switch button

CSS only toggle switch button
CssTutorial

The trend seems to be working with JavaScript more and more and to dislike CSS. However, as stated in a previous post about a Navbar that works without any JS, I really love building components with CSS only.

In a recent project I had to build an entire component library, and since I spent quite some time on a few components to match the designs that were sometimes challenging, I will share the most interesting ones in a series of blog posts.

This piece will be about the widely used toggle switch form element. I think it could come from the browsers in the future with a proper HTML tag, but I didn't see anything communicated in that way, in the meantime let's develop it! As for other CSS components, I aim a fair level of browser compatibility and accessibility.

Start from the checkbox element

This approach of starting our switch element from the existing checkbox will help greatly regarding accessibility and general usage. We see here after how we code our HTML. The parent div is not useful, but I like to have a wrapper element and the label must be after the input.

<div class="switch">
  <input type="checkbox" id="switch" />
  <label for="switch">Switch label</label>
</div>

At this stage, we will only ensure that our checkbox design is ignored completely by removing its appearance and margin.

.switch input[type='checkbox'] {
  appearance: none;
  display: block;
  margin: 0;
}

Add toggle switch typical design

Sadly, we cannot add before and after directly on a checkbox element as for the majority of form elements. We make use of the label element to helps us create the needed space for our toggle, but not only.

Here is are the few lines for the label itself.

.switch label {
  display: block;
  height: 2.375rem;
  position: relative;
  cursor: pointer;
  padding: 0.625rem 0 0.625rem 4.5rem;
}

The second bit of CSS for this will rely on the ::before pseudo-element to create the back of the switch.

.switch input[type='checkbox'] + label::before {
  content: 'No';
  display: block;
  position: absolute;
  top: calc(0.375rem - 1px);
  left: 0;
  padding: 0.375rem 0.5rem;
  height: 1.75rem;
  width: 3.75rem;
  text-align: right;
  font-size: 0.875rem;
  color: #ffffff;
  background-color: #8f8f8f;
  border-radius: 9999px;
  box-shadow: 0 0 0 1px #e0e0e0;
  transition: all 0.3s cubic-bezier(0.72, 0.26, 0.26, 0.82), visibility 0s;
}

The third part of CSS will focus on the ::after pseudo-element to create the front of the toggle.

.switch input[type='checkbox'] + label::after {
  content: '';
  display: block;
  position: absolute;
  top: calc(0.375rem - 1px);
  left: 0;
  height: 1.75rem;
  width: 1.75rem;
  background-color: #ffffff;
  border: solid 0.25rem #8f8f8f;
  border-radius: 9999px;
  transition: all 0.3s cubic-bezier(0.72, 0.26, 0.26, 0.82), visibility 0s;
}

Build the check logic

We designed the switch in the previous part; but how will we add the click logic to handle the check or uncheck state? Let's benefit from the :checked pseudo-class selector to modify the switch when it's in checked state. We are also using the + adjacent sibling combinator to target the label ::before and ::after. That's why we had to place the label after the checkbox input.

.switch input[type='checkbox']:checked + label::before {
  content: 'Yes';
  text-align: left;
  background-color: #008000;
}

.switch input[type='checkbox']:checked + label::after {
  left: 2rem;
  border-color: #008000;
}

Let's also add a focus state for better accessibility.

.switch input[type='checkbox']:focus + label::before,
.switch input[type='checkbox']:focus-visible + label::before {
  box-shadow: 0 0 0 4px #e0e0e0;
}

Handle the disabled and focus states

Last but not least, we have to design the switch to show a :disabled state, both :checked or not.

.switch input[type='checkbox']:disabled + label {
  cursor: not-allowed;
  &::before {
    background-color: #f5f5f5;
    color: #e0e0e0;
  }
  &::after {
    background-color: #e0e0e0;
    border-color: #f5f5f5;
  }
}

.switch input[type='checkbox']:checked:disabled + label {
  &::before {
    background-color: #f5f5f5;
    color: #707070;
  }
  &::after {
    background-color: #707070;
    border-color: #f5f5f5;
  }
}

If you are not at ease with Sass, you can copy and paste it and transpile to CSS on https://www.sassmeister.com/ or https://sass.js.org/.

Final switch look and feel

We made it! An accessible, good-looking and working switch toggle button. You may have noticed that it's also using rem which means it will adapt with the font-size of the html element.

TLDR: Find the complete solution for this CSS only toggle switch button on Codepen. Please be at peace taking inspiration from it and integrate it in your next project.

Enjoy coding with CSS!

Please open this website with a recent browser for the best experience. Avoid Internet Explorer at all costs.