Building a Checkbox Component with React and styled-components

A (mostly) hack-free approach

5 min readNov 3, 2018

--

Native checkboxes are notoriously difficult to style. Customization is tough because you cannot apply styles directly to the default checkbox element. Instead, it has become standard to hide the native checkbox element and replace it with a styled version. In this tutorial, we’ll see how we can use this standard “hack” to build a Checkbox component with React and styled-components.

Demo

Prerequisites

  • Familiarity with JavaScript and React
  • Node and npm installed

I recommend using nvm to install Node. npm is installed by default when you install Node.

  • React development environment

I recommend using create-react-app or CodeSandbox to quickly set up a React environment.

  • react, react-dom and styled-components installed

npm install react react-dom styled-components

Step 1: Wrapping the native checkbox element

Let’s start by wrapping the native checkbox element in a React component called Checkbox:

const Checkbox = props => (
<input type="checkbox" {...props} />
)

To see our Checkbox component in action, we’ll set up a simple stateful component that handles the state of our checkbox:

class ExampleApp extends React.Component {
state = { checked: false }
handleCheckboxChange = event =>
this.setState({ checked: event.target.checked })
render() {
return (
<div>
<label>
<Checkbox
checked={this.state.checked}
onChange={this.handleCheckboxChange}
/>
<span>Label Text</span>
</label>
</div>
)
}
}

It’s important to notice that unlike the native checkbox input, our Checkbox component must be wrapped by a label element. The label element will be solely responsible for triggering the onChange callback of the Checkbox since the native checkbox element will be visually hidden.

We now have a simple React component that wraps the native checkbox element. This component will be the foundation of our styled checkbox. We’ll build on of this simple component in the upcoming steps.

Step 2: Replacing the native checkbox element

Using the styled function from styled-components, we create a native checkbox element that is visually hidden:

const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
// Hide checkbox visually but remain accessible to screen readers.
// Source: https://polished.js.org/docs/#hidevisually
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`

Notice that we’re using .attrs() to provide default attributes to the input element that we’re creating with styled-components. Adding a type attribute of checkbox is what makes the input a checkbox instead of a text input.

Next, we apply our custom checkbox styles to a div that will replace the native checkbox:

const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: ${props => props.checked ? 'salmon' : 'papayawhip'};
border-radius: 3px;
transition: all 150ms;
`

styled-components allows us to apply styles based prop values. We use this feature to set the background color of our StyledCheckbox to salmon when props.checked is true and papayawhip otherwise. You can use the same technique to style the checked and unchecked state of any CSS property.

Now, we need to create a simple element to wrap HiddenCheckbox and StyledCheckbox. Let’s call it CheckboxContainer:

const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`

Let’s put together the components we just made to enhance our Checkbox component:

const Checkbox = ({ className, checked, ...props }) => (
<CheckboxContainer className={className}>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked} />
</CheckboxContainer>
)

We pass the checked prop to StyledCheckbox and HiddenCheckbox so both components are in-sync and displaying the correct state. Also note that it’s best practice to pass the className prop to the wrapping element so the component can be extended with styled-components

Step 3: Styling the focus state

To add custom styles to StyledCheckbox when the native checkbox is focused, we can reference the HiddenCheckbox component inside our definition of StyledCheckbox and apply special styles when HiddenCheckbox is focused:

const StyledCheckbox = styled.div`
...
${HiddenCheckbox}:focus + & {
box-shadow: 0 0 0 3px pink;
}
`

For this approach to work, HiddenCheckbox must be defined before StyledCheckbox.

Step 4: Adding a checkmark icon

A checkbox component is never complete without a checkmark icon. Let’s add that now. First, let’s create a svg element with some custom styles.

const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`

Feel free to change these properties to suite your needs.

Now, we use the Icon component to wrap a polyline element that defines the shape of the checkmark.

const Checkbox = ({ className, checked, ...props }) => (
<CheckboxContainer className={className}>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked}>
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
)

This SVG code comes from the check icon in Feather, however any SVG icon would work. You can learn more about SVGs here.

Now, let’s make sure that the Icon is only visible when the checkbox is checked:

const StyledCheckbox = styled.div`
...
${Icon} {
visibility: ${props => props.checked ? 'visible' : 'hidden'}
}
`

You now have a functional custom Checkbox component. In the next step, I’ll go over some ideas to enhance your checkbox.

Step 6: Enhancing your Checkbox component

The following are a few ideas to enhance your Checkbox component:

Exposing a size prop

You could have theCheckbox component expose a size prop. The size prop could be a string (e.g. "small" , "large" ) or a numeric pixel value (e.g. 16 , 20 ) or both.

Splitting up checked and unchecked styles

You could use the css function from styled-components to split up your checked and unchecked styles which might make your code more readable for complex checkbox styles. This might look something like this:

const checkedStyles = css`
...
`
const uncheckedStyles = css`
...
`
const StyledCheckbox = styled.div`
...
${props => props.checked ? checkedStyles : uncheckedStyles};
`

Styling the disabled state

You could use the same technique we used to styled the checked state to apply special styles when the checkbox is disabled.

Theming

You could use styled-components theming features to style your Checkbox component. This will work especially well if you are already using aThemeProvider in your app.

Found a mistake? Know a better way to implement a Checkbox component with React? Please let me know on twitter @colebemis

--

--

Building things for people who build things. Design Systems Engineer at GitHub. Studying Computer Science at CalPoly.