Creating a Switch Toggle in React using styled-components.

I always find myself every now and again creating a "Switch" or "Toggle" component for a client project.
After making them quite a few times I've decided to put my findings down in this post.
They can be super easy to make, and there's a few nuances that go with them. Let's begin.

Note: I've built this using the technologies I use the most: react, typescript and styled-components. But the CSS can be applied to any frontend stack :)

The whole component is built using just 4 components.
import styled from "styled-components";

const Label = styled.label``;
const Input = styled.input``;
const Switch = styled.div``;

const ToggleSwitch = () => {
  return (
    <Label>
      <span>Toggle is off</span>
      <Input />
      <Switch />
    </Label>
  );
};
This gives us something like this:
Now we actually don't want to show the <input>. But we do want it to be of type="checkbox".
This allows the user to be able to click on anything inside the <label> to trigger the onChange event, including our <span> element.

Note: It's important here to keep the input in the DOM by setting opacity: 0 and position: absolute. Why?

  • opacity: 0 will hide it from the user
  • position: absolute takes the element out of the normal doument flow.
  • This allows the user to "tab" to the label/input and use the spacebar to toggle the element.
  • const Input = styled.input`
      opacity: 0;
      position: absolute;
    `;
    
    // Set type to be "checkbox"
    <Input type="checkbox" />
    I'll add a few styles to the <label> component, it's wrapping everything, so I want it to be display: flex to align the <span> and <Switch /> vertically.
    The gap gives us a straight forward 10px gap between elements, and the cursor: pointer gives the user visual feedback saying "Hey! 👋 you can click me!".
    I'll also add styling to the <Switch /> element.
    const Label = styled.label`
      display: flex;
      align-items: center;
      gap: 10px;
      cursor: pointer;
    `;
    
    const Switch = styled.div`
      width: 60px;
      height: 32px;
      background: #b3b3b3;
      border-radius: 32px;
    `
    We now have something like this:
    Next up I'm going to create a pseudo-element on the <Switch /> element. This will act as our switches "lever".
    const Switch = styled.div`
      position: relative; /* <-- Add relative positioning */
      width: 60px;
      height: 32px;
      background: #b3b3b3;
      border-radius: 32px;
      padding: 4px; /* <!-- Add padding
    
      /* Add pseudo element */
      &:before {
        content: "";
        position: absolute;
        width: 28px;
        height: 28px;
        border-radius: 35px;
        top: 50%;
        left: 4px; /* <!-- Make up for padding
        background: white;
        transform: translate(0, -50%);
      }
    `;
    Now we have something that resembles a toggle switch:
    To animate the switch to be in the "on" position when it's pressed I need to move the const Switch = styled.div variable declaration to be above the const Input = styled.input variable. This is so we can reference the Switch from within Input.
    Using the :checked pseudo-class selector and the adjacent sibling combinator, we can make our switch turn green.
    const Input = styled.input`
      display: none;
    
      &:checked + ${Switch} {
        background: green;
      }
    `;
    Now in that same nested css structure, we can target the :before pseudo-element of the Switch element:
    const Input = styled.input`
      display: none;
    
      &:checked + ${Switch} {
        background: green;
    
        &:before {
          transform: translate(32px, -50%);
        }
      }
    `;
    Now all we have to do animate this into action is to add transition: 300ms to our Switch and the Switch :before pseudo-element
    const Switch = styled.div`
      position: relative;
      width: 60px;
      height: 28px;
      background: #b3b3b3;
      border-radius: 32px;
      padding: 4px;
      transition: 300ms all;
    
      &:before {
        transition: 300ms all;
        content: "";
        position: absolute;
        width: 28px;
        height: 28px;
        border-radius: 35px;
        top: 50%;
        left: 4px;
        background: white;
        transform: translate(0, -50%);
      }
    `;
    I'll add a basic onChange handler and useState hook to allow us to store the value of the checked input and change the text depending on the value:
    const ToggleSwitch = () => {
      const [checked, setChecked] = useState(false); // store value
    
      const handleChange = (e: ChangeEvent<HTMLInputElement>) => setChecked(e.target.checked)
    
      return (
        <Label>
          <span>Toggle is {checked ? 'on' : 'off'}</span>
          <Input checked={checked} type="checkbox" onChange={handleChange} />
          <Switch />
        </Label>
      );
    };
    And now we have a super simple working switch toggle:
    These things can be over-engineered sometimes, and there's also plenty of ways to recreate them.
    If you wanna follow me on twitter for dev-related tweets you can find me here

    25

    This website collects cookies to deliver better user experience

    Creating a Switch Toggle in React using styled-components.