Ok, the story is that, I’m really bad at css. I have never worked on building any frontend component and I was given a task to build the Custom Checkbox component with Reactjs from scratch. Here is how…
1. The basic HTML and CSS
Prepare the structure
Here is how you usually create a checkbox with pure html and css. To avoid any complicated event
handler, I will simply wrap the <label>
tag around, which allows clicking on any element inside to
transfer the event to the corresponding <input>
element without any Javascript needed.
<label class="mylabel">
<input class="myinput" type="checkbox" name="checkbox" />
<div class="mylabel">Checkbox label</div>
</label>
.mylabel {
display: flex;
gap: 5px;
align-items: center;
margin: 2px;
}
Try the live example in the below iframe (or direct link)
Styling the Checkbox
The fact is that you cannot style the checkbox element. Each browser decides its own appearance of the checkbox. The approach is to hide the checkbox completely and replace with another element just for the display.
Coming back to the above html structure, I’m going to add an empty <div>
(<span>
) after the
<input>
element as the placeholder for the display
<label class="mylabel">
<input class="myinput" type="checkbox" name="checkbox" />
<div class="mydisplay"></div> <!-- this is the new element -->
<div class="mylabel">Checkbox label</div>
</label>
And then I need to hide the real checkbox and add any style that I like to the newly added element. It’s just a normal element so you are not limited to just checkbox style.
.myinput {
display: none;
}
.myinput + .mydisplay {
width: 20px;
height: 20px;
border: 3px solid darkgray;
border-radius: 6px;
box-sizing: border-box;
}
Live example (or direct link)
Styling different states
Since we have wrapped the <label>
tag outside, clicking on any element inside will transfer the
event to the corresponding checkbox, which will then change its state. We can use that state to
style the display element like this
.myinput:hover + .mydisplay {
border-color: #00b3ee;
}
.myinput:checked + .mydisplay {
background-size: cover;
background-image: url("/files/2022-05-10-css-trick-building-custom-checkbox/check.png");
}
Live example (or direct link)
2. The Reactjs component
Converting this to a Reactjs component is quite straight forward with styled-components.
Here is the props type for our custom Checkbox component
type MyCheckboxProps = {
checked?: boolean;
text: string;
onChange?: (e: React.FormEvent<HTMLInputElement>) => void;
};
Here is how to convert those html to React component using styled-components
. Instead of using css
class selector, we can refer to the component directly using its identifier and &
import styled from 'styled-components';
import checkIcon from '/files/2022-05-10-css-trick-building-custom-checkbox/check.png';
const Label = styled.label`
display: flex;
gap: 5px;
align-items: center;
margin: 2px;
`;
const CheckboxDisplay = styled.div``;
const CheckboxText = styled.div``;
const CheckboxInput = styled.input.attrs((props: MyCheckboxProps) => ({
type: 'checkbox',
...props,
}))`
display: none;
& + ${CheckboxDisplay} {
width: 20px;
height: 20px;
border: 3px solid darkgray;
border-radius: 6px;
box-sizing: border-box;
}
&:hover + ${CheckboxDisplay} {
border-color: #00b3ee;
}
&:checked + ${CheckboxDisplay} {
background-size: cover;
background-image: url('${checkIcon}');
}
`;
Putting them altogether in the main component
function MyCheckbox({
checked = false,
text,
onChange
}: MyCheckboxProps): JSX.Element {
return (
<Label>
<CheckboxInput {...{checked, onChange}} />
<CheckboxDisplay />
<CheckboxText>{text}</CheckboxText>
</Label>
);
}
3. Handle other Checkbox states
In reality, there are more states that you may want to have for the Checkbox beside checked
and
unchecked
. The HTML checkbox provides 2 more different states to use with css indeterminate
and
invalid
. They require some small tweaks in Javascript to enable. In order to activate those
states, we need to access to the DOM node directly via
React Refs.
In the above MyCheckbox
component, add custom handler to update the ref properties
// add more props for the Checkbox component
type MyCheckboxProps = {
checked?: boolean;
indeterminate?: boolean;
invalid?: boolean;
text: string;
};
function MyCheckbox({
checked = false,
indeterminate = false,
invalid = false,
text
}: MyCheckboxProps): JSX.Element {
return (
<Label>
<CheckboxInput
{...{checked}}
ref={(instance: HTMLInputElement | null) => {
if (!instance) return;
// set indeterminate state
instance.indeterminate = indeterminate;
// set invalid state
instance.setCustomValidity(invalid ? 'error' : '');
}}
/>
<CheckboxDisplay/>
<CheckboxText>{text}</CheckboxText>
</Label>
);
}
And then styling in css is straight forward
const CheckboxInput = styled.input.attrs((props: MyCheckboxProps) => ({
type: 'checkbox',
...props,
}))`
/* ... same as previous CheckboxInput component */
/* ... style the new state */
&:indeterminate + ${CheckboxDisplay} {
background-color: #00b3ee;
}
&:invalid + ${CheckboxText} {
color: #ff0e29;
}
`;
Phew
That’s all. Improve it yourself!