How to Create a Reusable Custom Radio Button in React with Typescript
Without falling short on accessibility, let’s design and write a custom styled, reusable radio button in React with Typescript and StyledComponents.
Table of contents:
- What is a radio button? 🕵🏻♀️
- When to use radio buttons ⌚️
- Guidelines for designing radio buttons 🦮
- Design it (tutorial) 👨🏼🎨
- Code it (tutorial) 👩🏽💻
Radio buttons are a classic UI component meant for easy selection and decision-making. We all know them. We’ve all interacted with them. But every so often we come across radio buttons with terrible usability. However, we can’t put the blame solely on designers nor developers alone — it takes two to tango. 💃🕺
So, before we get started, let’s first review our lovable radio button; it’s purpose, when to use it, and the guidelines we need to follow.
A react-app repository with Typescript, and StyledComponents.
TL;DR: checkout the tutorial repo in Sandbox.
What is a radio button? 🕵🏻♀️
Well, it’s a selection control element, that allows our user to select one option from a list of options — opposite from checkboxes that instead allows a user to select multiple options. It’s represented as a small circle with a solid dot inside it when selected.
When to use radio buttons ⌚️
Radio buttons are more commonly used in forms, settings, or in surveys. More specifically, we use radio buttons in lists where two or more options are available, when the options have to be visible at the same time, and when it’s only possible to select one option.
Because radio buttons aren’t our only selection control tool, we need to make sure we’re choosing the right time to use radio buttons. Let’s go over a few rules:
Radio buttons vs. dropdown lists
It all comes down to the amount of options in a list, when making the decision on radio button vs dropdown. A good rule of thumb is to use a list of radio buttons if there are 5 or less options. If not, use a dropdown list. However, dropdown lists do require more from our users, both in terms of clicks and cognitive effort, so it’s good to try and simplify as much as possible.
Radio buttons vs. checkboxes
As mentioned above, checkboxes allows our user to select multiple options, compared to our single-selection radio buttons. It’s also important to note that while our user have to select one option in a list of radio buttons, they are free to select one, two or zero options in a list of checkboxes.
Guidelines for designing radio buttons 🦮
1. Ensure good usability
Most of the responsibility falls on a developer when it comes to a radio button’s usability, but there are things to take into account for designers as well. The number one rule, is that the radio button alone shouldn’t be the only clickable area and that we need to make sure our users can click anywhere between the actual button and the label — this is especially important for mobile.
2. Readability is crucial
To ensure good readability we need to make sure we’re not overwhelming our users with visual clutter. We can do so by putting together a logical layout for our radio button list: a vertical list. Vertical lists are more common and it’s easier to identify each line as one option. With horizontal lists, on the other hand, it’s easy to get confused by which label belongs to which button.
Aside from layout, we need to be consistent with our labels and make sure they’re short and to the point. The longer a label is, the more time and effort our users will need in order to grasp the list as a whole.
3. Radio buttons are for selection only
This goes out to developers as well: radio buttons are only meant for selecting an option, or value, never to perform an action. For example: when changing the date format settings on an app, our user has to first make the selection they want, before clicking a command button for those changes to be applied — the change should not take effect until the button has been clicked.
If you’re in need of an action after an interaction with an on/off-element, say to control a dark mode feature, you’d might want to replace it with a custom switch element.
4. Always define a default selection
To lessen the amount of actions needed from users, adding pre-selected values is a must. It allows our users to revert back to the original values in a form, so they can start over if needed. Which again results in less time and effort spent filling out something as boring as forms.
Design it (tutorial) 👨🏼🎨
We’ve covered the guidelines when designing a radio button, but it’s important to note that a radio button’s many states should be defined before the design hits development. A hot tip: look at the native radio button component and use that as a base when designing your custom radio button — you’ll be doing your users and developers a huge favor.
Traditionally, you’ll find the label on the button’s right hand side. To ensure readability, we’ll stick to that.
Lastly, we’ll add our brand colors:
And here’s the result:
In addition to a not-allowed cursor and faded colors, we’ll add a closed locker icon to further emphasize it’s disabled state. This is an added benefit for people who are color blind.
Code it (tutorial) 👩🏽💻
First we need to create our file:
and add the HTML markup of our component, we’ll fill it with mock data for now:
Now we need to connect the input and label-tag for assistive technologies to be able to identify the relationship between the two elements. We do this by adding an id to the input-tag and using the same id for htmlFor in the label-tag.
Add styling 👗✨
Now that we’ve set up the basics, we can start adding the custom styling to our radio button.
Let’s first add our brand colors to a global file for all our color values:
For this we’ll use an enum:
Now we can move on to styling our radio button. I like to keep my styled-components in a separate file. So the first thing we’ll do is to create a file for all of our different input styles:
..and style our Label component:
We’ll import the new label component to our radio button:
Before we continue, let us add styling to the div wrapping the two elements to match the design. Because this styling is unique to our RadioButton, we’ll leave the styled component in the same file:
Now comes the fun part: customizing our native radio button. In InputStyles.ts, we’ll create a new styled-component:
What we’ve done here is to visually remove the native styling to our radio button, by adding appearance: none. The element is still clickable and in our DOM — we just can’t see it. The border and border-radius values are giving us the outer circle to the radio button, while ::after gives us a pseudo-element that we can style as the inner dot. For the dot to be visible we need to add an empty string to content and define hight and width.
It’s time to add hover, focus, and checked styling:
Though a disabled radio button is an edge case, we’ll add it to our component — just in case:
And at last, import the styled-component to our radio button component:
Our component should now look like this:
Make it reusable ♻️
Now that we’ve got our style down, we can create a new file for our interface as this interface could be used by other input components:
Let’s also type out the first props we need for our component. We’ll set most of these to required.
Connect it to our component and give them an initial state:
Congrats, we’ve created a reusable radio button! 🎉✨
Ops! Don’t forget to add the disabled icon to our label. Start by creating a new file:
Create an icon for our disabled state by importing a lock-icon from styled-icons:
Add it to our radio button:
Make a reusable radio button group 🏘 ♻️
Radio buttons rarely come alone, which is why we’ll most likely be using a radio button group in our forms. We’ll have to use our radio button component to create the actual group component we’ll be needing. Let’s start by creating the component file:
We’ll also need to add a new interface. Since this interface could potentially be used with grouped input types, we’ll add it to InputInterface.ts.
Now we can set up the skeleton of our group before styling it. This time we’ll wrap it in a fieldset and use the legend-tag as a label.
For consistency, we’ll create a new styled-component to style our legend-tag the same as our label-tag:
And add it to our group component. What’s nice about fieldset and legend is that there’s no need for an aria-labelledby or role as it comes built-in.
Now that we’ve added our interface to our component we can set up a function to render our options:
The name-attribute is what connects our radio buttons as a group. Do NOT forget this as it is what makes a radio group a group. We’re also setting the first element in our option list to be checked by default.
Add extra styling 💅🏼
We can now add styling to the fieldset and the div wrapping our radio buttons. Because these are unique to our group component, we’ll keep them within the same file:
Cool! That should be it. Now we can move on the our final step:
Let’s put it to use 💪🏼
So in our parent component (we’ll just use App() for this example), we’ll add our new radio group component with a touch of some functionality to set selected value:
- Because we know the first option in a radio group is set to “checked” by default we’ll have to pass the first element in our option list to our selectedValue state.
- Our radioGroupHandler only set’s the new state. Any additional functionality should be done on a submit button.
Also: Don’t forget to test it with assistive tech! 🦮 🥳
And here’s the final result:
You’ll find the tutorial repo in SandBox.