How to style Rebass Switch

1.4k views Asked by At

Using Rebass/Forms in react and I cannot properly resize the Switch component using styles properly. (I also am using @emotion/styled)

I have tried using a size attribute, but that does not give the desired effect of simply changing the scale of the switch.

I have tried using the sx property and giving it a width and a height, but that only resizes the button element and not the inner div which is the "sliding dot"

I know that I could write some styling targeting the inner div itself, but I would like to find a way to give it a height and width a single time and it apply to both the button and the inner div.

<Switch
  sx={{ width: "30px", height: "15px" }}
/>

https://codesandbox.io/s/styling-rebass-switch-uu7wg

5

There are 5 answers

1
lastcanal On

It is not possible to do what you want 'out of the box' because the height and width are hard coded in the source

Luckily the internals of rebass are very nice, so it's possible to create your own with a little copy-pasta from the rebass source code.

import React from "react";
import { Box } from "reflexbox";

export const ResizableSwitch = ({
  checked,
  height = 24,
  width = 40,
  ...props
}) => (
  <Box
    as="button"
    type="button"
    role="switch"
    tx="forms"
    variant="switch"
    aria-checked={checked}
    {...props}
    __css={{
      appearance: "none",
      m: 0,
      p: 0,
      width,
      height,
      color: "primary",
      bg: "transparent",
      border: "1px solid",
      borderColor: "primary",
      borderRadius: 9999,
      "&[aria-checked=true]": {
        bg: "primary"
      },
      ":focus": {
        outline: "none",
        boxShadow: "0 0 0 2px"
      }
    }}
  >
    <Box
      aria-hidden
      style={{
        transform: checked ? `translateX(${width - height}px)` : "translateX(0)"
      }}
      sx={{
        mt: "-1px",
        ml: "-1px",
        width: height,
        height,
        borderRadius: 9999,
        border: "1px solid",
        borderColor: "primary",
        bg: "background",
        transitionProperty: "transform",
        transitionTimingFunction: "ease-out",
        transitionDuration: "0.1s",
        variant: "forms.switch.thumb"
      }}
    />
  </Box>
);

https://codesandbox.io/s/styling-rebass-switch-r6tmx?file=/src/App.js

0
SomoKRoceS On

Unfortunately, you can't. I took a deep look into the package itself and it seems like there is no fixed rule for the components written in this package. Some component do get the props and the sx. But there are components, like switch that hosts another component as a children, and no prop passed to it.

If you take a look at the implementation of switch in this page here:

export const Switch = forwardRef(({
  checked,
  ...props
}, ref) =>
  <Box
    ref={ref}
    as='button'
    type='button'
    role='switch'
    tx='forms'
    variant='switch'
    aria-checked={checked}
    {...props}
    __css={{
      appearance: 'none',
      m: 0,
      p: 0,
      width: 40,
      height: 24,
      color: 'primary',
      bg: 'transparent',
      border: '1px solid',
      borderColor: 'primary',
      borderRadius: 9999,
      '&[aria-checked=true]': {
        bg: 'primary',
      },
      ':focus': {
        outline: 'none',
        boxShadow: '0 0 0 2px'
      },
    }}>
    <Box
      aria-hidden
      style={{
        transform: checked ? 'translateX(16px)' : 'translateX(0)',
      }}
      sx={{
        mt: '-1px',
        ml: '-1px',
        width: 24,
        height: 24,
        borderRadius: 9999,
        border: '1px solid',
        borderColor: 'primary',
        bg: 'background',
        transitionProperty: 'transform',
        transitionTimingFunction: 'ease-out',
        transitionDuration: '0.1s',
        variant: 'forms.switch.thumb',
      }}
    />
  </Box>
)

There are 2 Box components (which are the base component of the package), one is a children of the other. The first Box is the area of the switch, and the child Box is the circle/button you are looking for, If you take a look on this component, you will see there is no outside variable that passed to it, so nothing can be changed - the style is already written.

This is the Button/Circle component:

    <Box
      aria-hidden
      style={{
        transform: checked ? 'translateX(16px)' : 'translateX(0)',
      }}
      sx={{
        mt: '-1px',
        ml: '-1px',
        width: 24,
        height: 24,
        borderRadius: 9999,
        border: '1px solid',
        borderColor: 'primary',
        bg: 'background',
        transitionProperty: 'transform',
        transitionTimingFunction: 'ease-out',
        transitionDuration: '0.1s',
        variant: 'forms.switch.thumb',
      }}
    />

If you are still willing to use that package, you can overcome this by overwriting the css, giving the component a className and apply styling to its children.

Plus, you can open an issue or suggest a fix on the package github repository.

2
Daniele Ricci On

Looking at Switch source code it seems no properties are propagated to the inner <div>... would you open an issue?

In the mean while you can set css properties for children and/or based on attributes:

.myswitch {
  width: 30px !important;
  height: 15px !important;
  background: gray !important;
}

.myswitch[aria-checked="true"] {
  background: red !important;
}

.myswitch div {
  width: 15px;
  height: 15px;
  background: red;
}

then:

<Switch className="myswitch" />

https://codesandbox.io/s/styling-rebass-switch-o0j8t

1
sudo bangbang On

You could use CSS transform scale to scale down/up an element and it's children. As, you're using emotion, here's something that goes along with it.

scale documentation

CodeSandbox: https://codesandbox.io/s/styling-rebass-switch-5fqku?file=/src/App.js

import React, { useState } from "react";
import styled from "@emotion/styled";
import { Label, Checkbox, Switch } from "@rebass/forms";

const Title = styled.h1`
  text-align: center;
`;

const FormLabel = styled(Label)`
  align-items: center;
`;

const Control = styled.div`
  width: 40px;
`;

const Toggle = styled(Switch)`
  transform: scale(.7)
`;


export default function App() {
  const [switched, setSwitched] = useState(false);
  const toggleSwitch = () => {
    setSwitched(!switched);
  };
  return (
    <div className="App">
      <Title>How to Style Rebass/Forms Switch</Title>
      <FormLabel sx={{ padding: "10px" }}>
        <Control>
          <Checkbox size="16px" sx={{ marginLeft: "10px" }} />
        </Control>
        CheckBox
      </FormLabel>
      <FormLabel sx={{ padding: "10px" }}>
        <Control>
          <Toggle
            checked={switched}
            onClick={() => toggleSwitch()}
          />
        </Control>
        Switch
      </FormLabel>
    </div>
  );
}
0
Velu S Gautam On

https://codesandbox.io/s/styling-rebass-switch-zto4z?file=/src/styles.css:37-1020

button.switch,
button.switch:hover,
button.switch:focus {
  outline: none;
  border: 1px solid grey;
  box-shadow: none;
}

button.switch > div {
  content: "";
  width: 14px;
  height: 14px;
  background-color: #9fa2ab;
}

button.switch > div:after {
  content: "";
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  left: -2px;
  top: -3px;
  transition: left 0.3s ease, background-color 0.3s ease, box-shadow 0.1s ease,
    transform 0.1s ease;
  background-color: #5b5c60;
  -webkit-box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
    0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
  box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
    0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}

button.switch[aria-checked="true"] {
  background-color: #d0f35e;
}
button.switch[aria-checked="true"] > div:after {
  background-color: #86af00;
}
button.switch[aria-checked="false"] {
  background-color: #ffffff;
  left: 18px;
}

Add class switch

<Switch
            
            className="switch"
            sx={{ width: "30px", height: "15px" }}
            checked={switched}
            onClick={() => toggleSwitch()}
          />