Building a Checkbox Component with React and styled-components
A (mostly) hack-free approach
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
andstyled-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