Foreword

Ever since I started to learn how to code I’ve had a passion for designing and creating beautiful looking web and mobile user interfaces. Recently I’ve adopted a sort of DIY mindset when it comes to implementing any kind of UI components in our React apps. I know it is not wise to re-invent the wheel when there exists so many awesome libraries/toolkits that provide you with the basic UI building blocks, such as Elemental UI and Ant Design, but I argue that in order to really deepen your UI/UX skills you should try to build your own wheel(s) at least once.

So, in this series you can tag along and observe how I create these various UI components with React and whatever CSS solution is the hottest in the streets (at the moment that’s styled-components for me). The components that I will be creating are relatively simple components such as Button, Tooltip, Modal, Card etc. to keep these posts concise.

Okay that’s enough blabber. Let’s get into our first UI component.

ToggleSwitch

In this first edition I will implement a simple toggle switch similar to the one used in iOS settings views.

Here’s a GIF showing how the simple version looks like:

iOS styled ToggleSwitch component

In addition to this simple form I will towards the end of this post add some extra functionalities like inner/outer labels that are configurable by props.

Design

I started by examining closely how the actual iOS toggle switch worked, trying to digest all the small details that contributed to the smooth user experience. These are the details I noticed:

Sometimes the ball has a small delay before the sliding animation starts, creating anticipation and providing more tangible interaction.

The sliding animation is not linear but goes slower at the start / end and faster in the middle.

The background color has a sort of growing / shrinking ripple effect when the switch is toggled.

The ball has a subtle shadow to make it more distinguishable and clickable.

With these observations the next step was to think about the properties my ToggleSwitch component would have. I knew I wanted to be able to control the different colors of the component with props, and possibly also the size of the component. Additionally, the user should be able to set the switch into the toggled position initially. So, in the end I came up with these defaultProps for the ToggleSwitch component:

const green = '#22e222';

const lightGrey = '#f5f5f5';

const grey = '#ddd';

const white = '#fff'; ToggleSwitch.defaultProps = {

initial: false,

width: 80,

padding: 3,

ballColor: white,

ballColorActive: lightGrey,

bgToggled: green,

bgClear: white,

borderColor: grey,

};

The “initial” prop should be self-describing, and the “width” and “padding” props are used to control the size while the rest of the props control the colors in different states.

Now we can take a look at how we can use these props in our styled components.

Implementation

The final React component looks like this:

class ToggleSwitch extends Component {

constructor(props) {

super(props);

this.toggle = this.toggle.bind(this);

this.state = {

toggled: props.initial || false,

};

} toggle() {

// Update local state first and then call toggle handler

this.setState(state => ({ toggled: !state.toggled }),

() => this.props.onToggle(this.state.toggled)

);

} render() {

return (

<ToggleSwitchWrapper>

<Toggle

onClick={this.toggle}

toggled={this.state.toggled}

{...this.props}

>

<ToggleBall

toggled={this.state.toggled}

{...this.props}

/>

<RippleBg

visible={this.state.toggled}

{...this.props}

/>

</Toggle>

</ToggleSwitchWrapper>

);

}

}

There’s only four sub-components in our ToggleSwitch component and the outermost ToggleSwitchWrapper is actually extra at the moment, but it will be needed in the future when we add some labels to the component.

So let’s skip ToggleSwitchWrapper and jump straight to the good stuff by examining the Toggle and RippleBg styled components.

const Toggle = styled.div`

display: flex;

align-items: center;

overflow: hidden;

position: relative;

transform: translate3d(0, 0, 0);

background-color: ${props => props.bgClear};

height: ${props => (props.width / 2)}px;

width: ${props => props.width}px;

border-radius: ${props => props.width / 4}px;

padding: ${props => props.padding}px;

border: 1px solid ${props => props.toggled ?

props.bgToggled :

props.borderColor

};

`;

The first interesting thing here is the transform: translate3d(0,0,0); part that allows us to take advantage of GPU muscle power in our animations. Without this line I noticed that the ripple animation (that I will show you in a sec) overflows out of the Toggle container component.

Another interesting thing here is how we use the props given to our component to define the width, height, padding, and border styles. In my opinion this ability to access props inside the styles is what makes styled-components so freaking amazing. If you haven’t tried it out you should definitely give it a go! End enthusiastic marketing voice.

Remember how we noticed in the design phase that the change of the background color had a ripple-like animation?

I tried to recreate that animation with the RippleBg styled component:

const RippleBg = styled.div`

width: 100%;

height: 100%;

top: 0;

left: 0;

position: absolute;

z-index: 1;

background-repeat: no-repeat;

background-position: 50%;

pointer-events: none;

transition: transform 0.5s, opacity 0.3s ease;

opacity: ${props => props.visible ? 1 : 0};

background-image: radial-gradient(

circle, ${props => props.bgToggled} 10%, transparent 10.01%

);

transform: ${props => props.visible ?

'scale(10, 10)' :

'scale(0, 0)'

};

`;

I have to admit that I shamelessly took the ripple animation from this amazing pure CSS ripple implementation since I knew that it would have taken too much time to figure out how to do it myself (speaking about hypocrisy — although when building your wheel you don’t necessary have to build the bolts too 😉).

The key details to notice here are the radial background-image and huge scaling transition to make the background fill the container with the ripple effect.

Alrighty, now we only have the ToggleBall left to look at, and here it is:

const ToggleBall = styled.div`

z-index: 2;

border-radius: 50%;

background-color: ${props => props.ballColor};

box-shadow: 0px 0px 3px rgba(0,0,0,0.2);

transition: transform 0.3s cubic-bezier(1,.19,.15,.7);

transition-delay: 0.1s;

will-change: transform;

border: 1px solid ${props => props.borderColor};

height: ${props => (props.width / 2) - (props.padding * 2)}px;

width: ${props => (props.width / 2) - (props.padding * 2)}px;

transform: ${props => props.toggled ?

`translateX(${props.width - (props.width / 2)}px)` :

'translateX(0px)'

}; &:active {

background-color: ${props => props.ballColorActive};

}

`;

The observations that we made in the design phase are taken into account here. The ball has a box-shadow to make it pop out and we apply a small delay to the transition to create that palm sweating anticipation. We also use a cubic-bezier curve to make the transition slow at the start, fast in the middle, and slow again in the end. If you are interested in how I created this curve, check this online tool for easy bezier curves. A thing to notice here is the will-change: transform; line which tells the browser what properties are about to change so that it can make some ahead of time optimizations before the property is actually changed. Using will-change can help you to reach that sweet 60fps goal for your animations.

Conclusion

Making this relatively simple toggle switch component was a blast! I learned a thing or two about making smooth animations, especially since I had never implemented a ripple animation before, so it was interesting to finally understand how it can be created with pure CSS. I think it really added UX value to the component.

Finally, I hope that I explained all the steps it took to create this component with enough detail so that nothing felt too magical or incomplete. I found that it’s surprisingly difficult to explain your thinking process that goes into designing and coding anything. Hopefully this series will have some use to you or at least motivate you to start your own DIY learning period as a web developer.

Here’s the full source code for the simple version without any labels: