Creating Custom Radio Groups
A few HTML form elements strike fear into some developers when the request for a custom-styled variant arises.
- The select element 😱
- The checkbox 🙀
- The radio button/input 😧
Today we grab the radio input and give it some finesse ✨ Our solution will be fully accessible and make use of our native radio input's focus and checked states to apply some styles to give us something like the below.
What is a Radio Group?
It is a set of checkable buttons, known as radio buttons, where no more than one of the buttons can be checked at a time.
Believe it or not but they are called radio buttons because they look and operate in a similar manner to the push buttons on old-fashioned radios, whereby only one button could be selected at a time.
Accessibility
Our solution will conform to the WCAG guidelines below thanks to us using the native radio input element as opposed to using alternative elements and having to apply aria attributes everywhere.
-
Tab
 andÂShift + Tab
should move focus into and out of the radio group. When the focus moves to a radio group:- If a radio button is checked, the focus is set on the checked button.
- If none of the radio buttons are checked, the focus should be set on the first radio button in the group.
Space
should check the focused radio button if it is not already checked.Right Arrow
 andÂDown Arrow
should move focus to the next radio button in the group, uncheck the previously focused button, and check the newly focused button. If the focus is on the last button, the focus moves to the first button.Left Arrow
 andÂUp Arrow
should move focus to the previous radio button in the group, uncheck the previously focused button, and check the newly focused button. If the focus is on the first button, the focus moves to the last button.
Creating our HTML
Let's start off by creating a radio group component which will be the parent of our radio button children and add the role of radiogroup
to signal this information to assistive technologies.
Next up is our Radio component. The idea here is to use the label element to style our custom radio button. The advantage is that our label is associated with an input element via the htmlFor
React attribute, so interactions with our label will propagate to our radio input allowing us to style the label based on the input element's state.
import React, { PropsWithChildren } from "react";
export const RadioGroup = ({ children }: PropsWithChildren<{}>) => {
return (
<div
role="radiogroup"
className="radio-group"
aria-labelledby="group_heading"
>
<h2 id="group_heading">Select a button</h2>
{children}
</div>
);
};
export const Radio = ({
children,
id,
name
}: PropsWithChildren<{ id: string; name: string }>) => {
return (
<>
<input type="radio" id={id} name={name} />
<label className="radio-label" htmlFor={id}>
{children}
</label>
</>
);
};
Styles
* {
font-family: "Inter";
--grey: #ededed;
--blue: #0037fc;
}
h2 {
margin: 0;
font-weight: bold;
}
p {
margin-bottom: 0;
}
.radio-group {
display: flex;
gap: 16px;
margin-top: 32px;
}
/* Hide the native radio input */
input[type="radio"] {
appearance: none;
opacity: 0;
position: absolute;
}
label {
position: relative;
padding: 48px 16px;
border-radius: 8px;
box-shadow: 2px 2px 6px 0px var(--grey);
}
label::after {
content: "";
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
right: 16px;
top: 16px;
background: var(--grey);
}
/* When the radio is checked, style the label accordingly */
input[type="radio"]:checked + label {
background: #ebefff;
outline: solid 3px var(--blue);
}
/* When the radio is focused, style the label accordingly */
input[type="radio"]:focus + label {
outline-offset: 3px;
}
/* When the radio is checked, style the pseudo-element accordingly */
input[type="radio"]:checked + label::after {
background: var(--blue);
}
A Code Sandbox is available below.
https://codesandbox.io/s/nameless-tree-d8yhww
Summary
Our solution conforms to the WCAG guidelines when using the keyboard and we have accessible styling applied when our radio buttons are in their focus and checked states.
We've achieved this by setting our input and label elements alongside each other, just like the happy siblings they are.
Adios.
Top comments (0)