DEV Community

Cover image for Creating a Switch Toggle in React using styled-components.
Karl Taylor
Karl Taylor

Posted on

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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dccyj0x0qnmwgwg7h5k.jpg)

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.


```tsx
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.

```tsx
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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86wlp27nq4aqwnukanz3.png)

Next up I'm going to create a [pseudo-element](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements) on the `<Switch />` element. This will act as our switches "lever".

```tsx
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:

![Toggle Switch Off](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7tsnh133h7gqx5025xe.jpg)

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](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements) selector and the [adjacent sibling combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator), we can make our switch turn green.

```tsx
const Input = styled.input`
  display: none;

  &:checked + ${Switch} {
    background: green;
  }
`;
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njqr2tvdm8g2nx5wvxw0.jpg)

Now in that same nested css structure, we can target the `:before` pseudo-element of the `Switch` element:

```tsx
const Input = styled.input`
  display: none;

  &:checked + ${Switch} {
    background: green;

    &:before {
      transform: translate(32px, -50%);
    }
  }
`;
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrdw74swfzupkpusv889.jpg)

Now all we have to do animate this into action is to add `transition: 300ms` to our `Switch` and the `Switch` `:before` pseudo-element

```tsx
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:

```tsx
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:

Here's a [CodeSandbox link](https://codesandbox.io/s/easy-toggle-tnimz?file=/src/App.tsx)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vb2vergstpkj70vojuf.gif)

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](https://twitter.com/karlcodes_)

Enter fullscreen mode Exit fullscreen mode

Top comments (0)