How To Make Switch Component with React

December 1, 2022

2 min read

How To Make Switch Component with React
Watch on YouTube

Let's make a nice switch or toggle component with React.

switch

In props, we have value and onChange handler together with an optional label that we'll show next to the switch.

import styled, { useTheme } from "styled-components"
import { defaultTransitionCSS } from "../animations/transitions"
import { CheckIcon } from "../icons/CheckIcon"
import { CloseIcon } from "../icons/CloseIcon"
import { HStack } from "../Stack"
import { Text } from "../Text"
import { centerContentCSS } from "../utils/centerContentCSS"
import { getCSSUnit } from "../utils/getCSSUnit"
import { getSameDimensionsCSS } from "../utils/getSameDimensionsCSS"
import { roundedCSS } from "../utils/roundedCSS"

interface SwitchProps {
  value: boolean
  onChange: (value: boolean) => void
  label?: string
}

const height = 28
const width = height * 1.58
const spacing = 2
const controlSize = height - spacing * 2

const Control = styled.div`
  ${getSameDimensionsCSS(controlSize)};

  ${roundedCSS};
  ${defaultTransitionCSS};

  ${centerContentCSS};
  color: ${({ theme }) => theme.colors.background.toCssValue()};
  font-size: 14px;
`

const Wrapper = styled(HStack)`
  cursor: pointer;

  color: ${({ theme }) => theme.colors.textSupporting.toCssValue()};

  &:hover {
    color: ${({ theme }) => theme.colors.text.toCssValue()};
  }

  &:hover ${Control} {
    transform: scale(1.08);
  }
`

const Container = styled.div`
  width: ${getCSSUnit(width)};
  height: ${getCSSUnit(height)};

  display: flex;
  align-items: center;

  ${roundedCSS};
  ${defaultTransitionCSS};
`

export const Switch = ({ value, onChange, label }: SwitchProps) => {
  const { colors } = useTheme()
  return (
    <Wrapper
      onClick={() => onChange(!value)}
      as="label"
      alignItems="center"
      gap={8}
      id={label}
    >
      <Container
        style={{
          background: (value
            ? colors.textSupporting3
            : colors.backgroundGlass2
          ).toCssValue(),
        }}
      >
        <Control
          style={{
            marginLeft: value ? width - controlSize - spacing : spacing,
            background: colors.text.toCssValue(),
          }}
        >
          {value ? <CheckIcon /> : <CloseIcon />}
        </Control>
      </Container>
      {label && <Text>{label}</Text>}
    </Wrapper>
  )
}

First, we render the Wraper that has the onClick handler so the user can also click on the text to toggle the value. On hover, we'll change the color and make the circle larger with the transform scale.

Then we display the container with fixed width and height. Its background will also change based on value, so the unchecked variant will have an almost transparent background.

Control has the size of container height minus two spacings. To make it move with animation, we set a transition and change the marginLeft property based on value.

I've designed the component for my productivity app Increaser. It utilizes a lot of colors already because every project has different colors. That's why it based on grayish colors and has an icon inside the circle to make it more intuitive for the user.