create-a-toggle-switch-in-react-as-a-reusable-component

In this article, we’re going to create an iOS-inspired toggle switch using React components. By the end, we’ll have built a simple demo React App that uses our custom toggle switch component.

We could use third-party libraries for this, but building from scratch allows us to better understand how our code is working and allows us to customize our component completely.

Forms provide a major means for enabling user interactions. The checkbox is traditionally used for collecting binary data — such as yes or no, true or false, enable or disable, on or off, etc. Although some modern interface designs steer away from form fields when creating toggle switches, I’ll stick with them here due to their greater accessibility.

Here’s a screenshot of the component we’ll be building:

The final result

Getting Started

We can start with a basic HTML checkbox input form element with its necessary properties set:


To build around it, we might need an enclosing

with a class, a and the control itself. Adding everything, we might get something like this:

In time, we can get rid of the label text and use the tag to check or uncheck the checkbox input control. Inside the , let’s add two s that help us construct the switch holder and the toggling switch itself:

Converting to a React Component

Now that we know what needs to go into the HTML, all we need to do is to convert the HTML into a React component. Let’s start with a basic component here. We’ll make this a class component, and then we’ll convert it into hooks, as it’s easier for new developers to follow state than useState:

import React, { Component } from "react";

class ToggleSwitch extends Component {
  render() {
    return (
      
); } } export default ToggleSwitch;

At this point, it’s not possible to have multiple toggle switch sliders on the same view or same page due to the repetition of ids. We could leverage React’s way of componentization here, but in this instance, we’ll be using props to dynamically populate the values:

import React, { Component } from "react";

class ToggleSwitch extends Component {
  render() {
    return (
      
); } } export default ToggleSwitch;

The this.props.Name will populate the values of id, name and for (note that it is htmlFor in React JS) dynamically, so that you can pass different values to the component and have multiple of them on the same page. Also, the tag doesn’t have an ending tag; instead it’s closed in the starting tag like , and this is completely fine.

Styling and CSS

I recently wrote 8 Ways to Style React Components, and we’ll use SCSS here (which I consider the best way). Since our SCSS file is already included through the starting index.js script, we don’t need to include another SCSS file again in the component itself. Let’s first have a look how the base CSS is done. After that, we’ll make improvements on it. A few things that we’ll be doing with the styling are as follows:

  • By default, the switch is going to be only 75px wide and vertically aligned inline-block so that it’s inline with the text and doesn’t cause layout problems.
  • We’ll make sure that the control is not selectable so that users can drag and drop it.
  • We’ll also be hiding the original checkbox input.
  • Both the ::after and ::before needs to be styled and made as elements to get them into the DOM and style it.
  • We’ll also add some CSS transitions to make it look cool and animated.
.toggle-switch {
  position: relative;
  width: 75px;
  display: inline-block;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  text-align: left;
}
.toggle-switch-checkbox {
  display: none;
}
.toggle-switch-label {
  display: block;
  overflow: hidden;
  cursor: pointer;
  border: 0 solid #ccc;
  border-radius: 20px;
  margin: 0;
}
.toggle-switch-inner {
  display: block;
  width: 200%;
  margin-left: -100%;
  transition: margin 0.3s ease-in 0s;
}
.toggle-switch-inner::before, .toggle-switch-inner::after {
  display: block;
  float: left;
  width: 50%;
  height: 34px;
  padding: 0;
  line-height: 34px;
  font-size: 14px;
  color: white;
  font-weight: bold;
  box-sizing: border-box;
}
.toggle-switch-inner:before {
  content: "Yes";
  text-transform: uppercase;
  padding-left: 10px;
  background-color: #f90;
  color: #fff;
}
.toggle-switch-disabled {
  background-color: #ccc;
  cursor: not-allowed;
}
.toggle-switch-disabled::before {
  background-color: #ccc;
  cursor: not-allowed;
}
.toggle-switch-inner::after {
  content: "No";
  text-transform: uppercase;
  padding-right: 10px;
  background-color: #ccc;
  color: #fff;
  text-align: right;
}
.toggle-switch-switch {
  display: block;
  width: 24px;
  margin: 5px;
  background: #fff;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 40px;
  border: 0 solid #ccc;
  border-radius: 20px;
  transition: all 0.3s ease-in 0s;
}
.toggle-switch-checkbox:checked   .toggle-switch-label .toggle-switch-inner {
  margin-left: 0;
}
.toggle-switch-checkbox:checked   .toggle-switch-label .toggle-switch-switch {
  right: 0px;
}

If we look at this, the "Yes" and "No" can be sent to here dynamically from the control using data-* attributes in HTML5. This is required, because it’s not always the same "Yes" and "No" values. Let’s make it dynamic for now:

.toggle-switch-inner::before {
  content: attr(data-yes);
  /* other styles */
}
.toggle-switch-inner::after {
  content: attr(data-no);
  /* other styles */
}

Also, it would be a great idea to use a smaller version of switch, without the text, so let’s add some more CSS for it with some minimal sizes and removing the text:

.toggle-switch.small-switch {
  width: 40px;
}
.toggle-switch.small-switch .toggle-switch-inner:after, .toggle-switch.small-switch .toggle-switch-inner:before {
  content: "";
  height: 20px;
  line-height: 20px;
}
.toggle-switch.small-switch .toggle-switch-switch {
  width: 16px;
  right: 20px;
  margin: 2px;
}

With respect to responsiveness, we should be changing the complete size. For a hacky version, we’re going to use the CSS scale function. Here we’ve covered all the Bootstrap-based responsive widths of devices:

@media screen and (max-width: 991px) {
  .toggle-switch {
    transform: scale(0.9);
  }
}
@media screen and (max-width: 767px) {
  .toggle-switch {
    transform: scale(0.825);
  }
}
@media screen and (max-width: 575px) {
  .toggle-switch {
    transform: scale(0.75);
  }
}

Converting the above code to SCSS, we’ll get something like:

.toggle-switch {
  position: relative;
  width: 75px;
  display: inline-block;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  text-align: left;
  &-checkbox {
    display: none;
  }
  &-label {
    display: block;
    overflow: hidden;
    cursor: pointer;
    border: 0 solid #ccc;
    border-radius: 20px;
    margin: 0;
  }
  &-inner {
    display: block;
    width: 200%;
    margin-left: -100%;
    transition: margin 0.3s ease-in 0s;
    &:before,
    &:after {
      display: block;
      float: left;
      width: 50%;
      height: 34px;
      padding: 0;
      line-height: 34px;
      font-size: 14px;
      color: white;
      font-weight: bold;
      box-sizing: border-box;
    }
    &:before {
      content: attr(data-yes);
      text-transform: uppercase;
      padding-left: 10px;
      background-color: #f90;
      color: #fff;
    }
  }
  &-disabled {
    background-color: #ccc;
    cursor: not-allowed;
    &:before {
      background-color: #ccc;
      cursor: not-allowed;
    }
  }
  &-inner:after {
    content: attr(data-no);
    text-transform: uppercase;
    padding-right: 10px;
    background-color: #ccc;
    color: #fff;
    text-align: right;
  }
  &-switch {
    display: block;
    width: 24px;
    margin: 5px;
    background: #fff;
    position: absolute;
    top: 0;
    bottom: 0;
    right: 40px;
    border: 0 solid #ccc;
    border-radius: 20px;
    transition: all 0.3s ease-in 0s;
  }
  &-checkbox:checked   &-label {
    .toggle-switch-inner {
      margin-left: 0;
    }
    .toggle-switch-switch {
      right: 0px;
    }
  }
  &.small-switch {
    width: 40px;
    .toggle-switch-inner {
      &:after,
      &:before {
        content: "";
        height: 20px;
        line-height: 20px;
      }
    }
    .toggle-switch-switch {
      width: 16px;
      right: 20px;
      margin: 2px;
    }
  }
  @media screen and (max-width: 991px) {
    transform: scale(0.9);
  }
  @media screen and (max-width: 767px) {
    transform: scale(0.825);
  }
  @media screen and (max-width: 575px) {
    transform: scale(0.75);
  }
}

Theming in SCSS

Since we can use variables in SCSS, theming becomes easier. Adding support for multiple color themes in our app is made easier using SCSS. Sass Theming: The Neverending Story explains some of it. We’ll be using some color themes here and change all the raw colors to variables. The first three lines are a configurable set of colors, which helps us theme our little control:

// Colors
$label-color: #ccc;
$toggle-color: #f90;
$white: #fff;

// Styles
.toggle-switch {
  position: relative;
  width: 75px;
  display: inline-block;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  text-align: left;
  &-checkbox {
    display: none;
  }
  &-label {
    display: block;
    overflow: hidden;
    cursor: pointer;
    border: 0 solid $label-color;
    border-radius: 20px;
    margin: 0;
  }
  &-inner {
    display: block;
    width: 200%;
    margin-left: -100%;
    transition: margin 0.3s ease-in 0s;
    &:before,
    &:after {
      display: block;
      float: left;
      width: 50%;
      height: 34px;
      padding: 0;
      line-height: 34px;
      font-size: 14px;
      color: white;
      font-weight: bold;
      box-sizing: border-box;
    }
    &:before {
      content: attr(data-yes);
      text-transform: uppercase;
      padding-left: 10px;
      background-color: $toggle-color;
      color: $white;
    }
  }
  &-disabled {
    background-color: $label-color;
    cursor: not-allowed;
    &:before {
      background-color: $label-color;
      cursor: not-allowed;
    }
  }
  &-inner:after {
    content: attr(data-no);
    text-transform: uppercase;
    padding-right: 10px;
    background-color: $label-color;
    color: $white;
    text-align: right;
  }
  &-switch {
    display: block;
    width: 24px;
    margin: 5px;
    background: $white;
    position: absolute;
    top: 0;
    bottom: 0;
    right: 40px;
    border: 0 solid $label-color;
    border-radius: 20px;
    transition: all 0.3s ease-in 0s;
  }
  &-checkbox:checked   &-label {
    .toggle-switch-inner {
      margin-left: 0;
    }
    .toggle-switch-switch {
      right: 0px;
    }
  }
  &.small-switch {
    width: 40px;
    .toggle-switch-inner {
      &:after,
      &:before {
        content: "";
        height: 20px;
        line-height: 20px;
      }
    }
    .toggle-switch-switch {
      width: 16px;
      right: 20px;
      margin: 2px;
    }
  }
  @media screen and (max-width: 991px) {
    transform: scale(0.9);
  }
  @media screen and (max-width: 767px) {
    transform: scale(0.825);
  }
  @media screen and (max-width: 575px) {
    transform: scale(0.75);
  }
}

Interactions and JavaScript

Now let’s concentrate on getting the basic component working. Since everything in React happens on the fly, we need to use states for storing local component information. Just a friendly reminder: whenever you change any value in the state of a component, the render() life cycle method of React JS will be fired. Keeping that in mind, let’s build our default state:

state = {
  checked: this.props.defaultChecked
};

All we require is just the checked state. It’s going to be a boolean value and it will be received from a defaultChecked prop of the component. We’ll be using a static defaultProps in our class component as a fallback. This looks something similar to the following code:

// Set text for rendering.
static defaultProps = {
  Text: ["Yes", "No"]
}

Since most of the props have to be user set and we can’t use arbitrary values, it’s always better to stop rendering if the required props aren’t passed on. This can be done using a simple JavaScript if statement or a ternary operator using ? : or a short-circuited “and” &&:

{this.props.id ? (
  
) : null}

We might as well add the event listeners using the props for the component like onChange. Along with that, we can also add few more props for the following:

  • id (required): this is the id that’s going to be passed to the checkbox input control. Without this, the component won’t render.
  • Text (required): if you aren’t using the Small version of the control, you might need to pass this to the Toggle Switch as an array of two values, which signify the text for True and False. An example would be Text={["Yes", "No"]}.
  • Name (optional): this will be label text of the checkbox input, but we generally won’t be using this.
  • onChange (optional): this will be directly passed to the .
  • defaultChecked (optional): this will be directly passed to the .
  • Small (optional): this is a boolean value, which renders the Toggle Switch in a small mode, where the text isn’t rendered.
  • currentValue (optional): this will be directly passed to the as defaultValue.
  • disabled (optional): this will be directly passed to the .

As our app grows, we can catch a lot of bugs with type checking. React has some built-in type-checking abilities. To run type checking on the props for a component, you can assign the special propTypes property. We can enforce the above list of props using React’s PropType library, which is a separate library that exports a range of validators that can be used to make sure the data you receive is valid.

In this example, we’re using the following:

  • PropTypes.string.isRequired: this is a string value and it’s required and mandatory.
  • PropTypes.string: this is a string value but it isn’t mandatory.
  • PropTypes.func: this is a prop that takes in a function as value but it isn’t mandatory.
  • PropTypes.bool: this is a boolean value but it isn’t mandatory.

You need to import the PropTypes library using:

import PropTypes from "prop-types";

And after the class definition and before the export statement, if you have it separately, we’ll define the PropTypes in the following way.

ToggleSwitch.propTypes = {
  id: PropTypes.string.isRequired,
  Text: PropTypes.string.isRequired,
  Name: PropTypes.string,
  onChange: PropTypes.func,
  defaultChecked: PropTypes.bool,
  Small: PropTypes.bool,
  currentValue: PropTypes.bool,
  disabled: PropTypes.bool
};

With all the above said elements, our component now looks like this:

import React, { Component } from "react";
import PropTypes from "prop-types";

/*
Toggle Switch Component
Note: id is required for ToggleSwitch component to function. Name, currentValue, defaultChecked, Small and onChange're optional.
Usage: 
*/

class ToggleSwitch extends Component {
  state = {
    checked: this.props.defaultChecked
  };
  onChange = e => {
    this.setState({
      checked: e.target.checked
    });
    if (typeof this.props.onChange === "function") this.props.onChange();
  };
  render() {
    return (
      
{this.props.id ? ( ) : null}
); } // Set text for rendering. static defaultProps = { Text: ["Yes", "No"] }; } ToggleSwitch.propTypes = { id: PropTypes.string.isRequired, Text: PropTypes.string.isRequired, Name: PropTypes.string, onChange: PropTypes.func, defaultChecked: PropTypes.bool, Small: PropTypes.bool, currentValue: PropTypes.bool, disabled: PropTypes.bool }; export default ToggleSwitch;

Unit Testing

Any code that has been created needs to be unit tested, at least to a basic level. In my current workplace, we’re using a snapshot based unit testing. We’ll be checking for the following conditions in the component. We’re using the library enzyme by AirBNB for testing purposes and jest as the default test runner. By writing these tests, we’re making sure our Toggle Switch component:

  • should render without crashing
  • should match the snapshot
  • should fail if the ID isn’t supplied
  • should render if the ID is supplied
  • should disable the switch slider if it contains disabled props

Here’s the complete ToggleSwitch.test.js:

import React from "react";
import { shallow } from "enzyme";
import ToggleSwitch from "../components/ToggleSwitch";

const text = ["Yes", "No"];
const chkID = "checkboxID";

describe("Toggle Switch Component", () => {
  it("should render without crashing", () => {
    const ToggleSwitchComponent = shallow();
    expect(ToggleSwitchComponent.html()).not.toHaveLength(0);
  });

  it("should match snapshot", () => {
    const ToggleSwitchComponent = shallow();
    expect(ToggleSwitchComponent).toMatchSnapshot();
  });

  it("should fail if id is not supplied", () => {
    const ToggleSwitchComponent = shallow();
    expect(ToggleSwitchComponent.find("label")).toHaveLength(0);
  });

  it("should render if id is supplied", () => {
    const ToggleSwitchComponent = shallow(
      
    );
    expect(ToggleSwitchComponent.find("label")).not.toHaveLength(0);
  });

  it("should disable switch slider if it contains disabled props", () => {
    const ToggleSwitchComponent = shallow(
      
    );
    expect(ToggleSwitchComponent.find("#"   chkID).props().disabled).toBe(true);
  });
});

Summary

You can get the complete source code from praveenscience/ToggleSwitch: Implementing a Toggle Switch in React JS as a Reusable Component. The usage instructions are updated in the GitHub Repository.

I hope you’ve found this article useful. If you have any comments or questions, please hit me up on Twitter.

Praveen is a software and web developer, cloud computing consultant, full-stack developer, UX architect, a CEO, and even … a cook. You can find him at praveen.science.

why-toggle-tokens-are-a-better-alternative-to-checkboxes

What interface component would you use for selecting from a large set of options? For most designers, checkboxes come to mind. But a long list of checkboxes is intimidating to users and can cause them to abandon your form. Not only that, but checkboxes are not efficient or easy to use because they take up space, increase the number of visual elements, and offer small tap targets.

A better component for option selection is toggle tokens. Toggle tokens conserve vertical space so you have room for more content and users don’t have to scroll. Checkboxes require vertical stacking, but toggle tokens allow for both vertical and horizontal stacking. This creates a compact arrangement that makes it less intimidating for users.

toggle-tokens-checkboxes

Not only that, but toggle tokens don’t require a checkbox and checkmark with the label. As a result, there are fewer elements on the screen competing for the user’s attention. Minimizing visual noise allows users to focus on the options.

The small tap targets of checkboxes can also cause tapping issues. Toggle tokens offer larger tap targets so users can make selections without mistapping.

All these benefits make toggle tokens a better component for selecting options than checkboxes. However, there is an exception when checkboxes fare better.

If your options have long text labels that wrap to multiple lines, you should use checkboxes. Checkbox labels aren’t horizontally constrained and allow enough space for more text. On the other hand, toggle tokens are constrained by its background shape and should only be used when your options are single text line labels.

toggle-tokens-labels

The name “toggle token” is also as intuitive as the name “checkbox.” It comes from its token-like shape and toggle functionality. Next time you’re thinking about using checkboxes for option selection, consider toggle tokens instead. You’ll conserve screen space and simplify the interface, which will prevent users from abandoning your form.

FP-tout

PP-tout

stop-misusing-toggle-switches

There are times to use toggle switches and times not to. When designers misuse them, it leads to confused and frustrated users. Knowing when to use them requires understanding the different types of toggle states and options.

Contextual States Vs. System States

It’s easy for designers to confuse toggle switches and toggle buttons because they both manage states, but there’s a fundamental difference. Toggle switches are for system states, and toggle buttons are for contextual ones. A contextual state only affects the current screen in focus, while a system state takes effect everywhere on the app.

contextual-system-states

Many apps misuse toggle switches by using them for contextual states. For example, a common mistake is to use switches for search filters. The filters only apply to the context of search, not to the entire system. Therefore, the proper selection controls to use are checkboxes, not switches.

toggle-system-state

Users expect switches to render an immediate effect when they toggle it on. However, the search filter settings don’t take effect until after users press the “save” button. If there’s a delayed effect due to a separate button, switches are the wrong controls to use. A switch itself is a “button” that activates state. A separate button for the switch isn’t necessary.

toggle-checkboxes

It’s typical to find switches in a settings screen of an app because that’s where users go to manage system states. But you can also use them for modes that affect the app. The example below shows switches for privacy mode and dark mode.

toggle_switch-modes

Binary Options Vs. Opposing Options

Switches are for binary options, not opposing options. A binary option represents a single state that is either on or off — or in other words, true or false. Opposing options are two separate states that are opposite but related to different user tasks.

toggle_switch-opposing_options

Some apps make the mistake of using switches for opposing options. They place the option labels on opposite sides of the switch and use the direction of the thumb to indicate the state. This practice is a misuse of switches that confuses users because the visual cue isn’t clear. Not only that, but the switch has two different states without an off state as they would expect.

When you have opposing options, toggle buttons are the right control to use. In the example, “list view” and “map view” are the opposing options for users to toggle. A toggle button in this context works better than a switch because it groups the options and allows users to view them side-by-side. They’re also able to select each option directly and get clear visual feedback.

States Vs. Actions

Switches are for states and buttons are for actions. You should never use a switch in place of a button, or you’ll throw users off. When they see a call to action, they expect to interact with a button, not a switch.

toggle-button-action

The example shows an app using a toggle switch as a download button. This approach is a misuse of switches because downloading is a one-time action, not a persistent state. Turning the switch on downloads the content but ends after that. Turning the switch off doesn’t undownload it, which can mislead users.

Three Conditions for Using Switches

Next time you’re considering a toggle switch on your app, check if the situation meets these three conditions. If it does, you’ll know for sure that a toggle switch is the correct control to use.

Use a toggle switch if you are:


1. Applying a system state, not a contextual one


2. Presenting binary options, not opposing ones


3. Activating a state, not performing an action

FP-tout

PP-tout