How to Create Custom Formik Radio Button / Radio Group (with Typescript)
Jasser Mark Arioste
Hello, hustlers! In this tutorial, you’ll learn how to create a custom accessible formik radio button component using HeadlessUI and TailwindCSS! We’ll also implement it with Typescript.
What is HeadlessUI?
HeadlessUI is a set of fully unstyled and accessible components for React or Vue. Since they’re already accessible, we don’t have to implement features like keyboard functionality. And since they’re unstyled, we have complete control of the styling. It’s designed to be easily integrated with Tailwind CSS as well.
What is Tailwind CSS?
Tailwind CSS is a set of utility CSS classes to style your HTML components.
Why use HeadlessUI + TailwindCSS?
We’re using these two libraries to have complete customizability for our components.
I like using Tailwind CSS since CSS Classes can be used anywhere, so it’s effortless to integrate Tailwind into new or existing projects. Using the tailwind VSCode extension , we have complete intellisense and documentation inside VScode without going to the website. It also keeps the CSS bundle size to a minimum since it only bundles the classes used in the code base.
This makes it very easy to style components in general and results in a fast development time.
Step 0 - Project Setup & Installing the Dependencies
If you already have tailwindcss or headlessui installed for your project, you may proceed to Step 2.
For this tutorial, I’m using NextJS as our react framework. In my experience, NextJS + TailwindCSS is such a joy to work with. If you’re using a different framework in your project, make sure you check the tailwind’s installation docs .
First, let’s create a brand new NextJS project:
npx create-next-app --ts formik-custom-radio-button-tutorial
Once that’s done, let’s install tailwindcss , headlessui/react and formik packages all in one go. We’ll also use the classnames package to manage the classes later on.
# npm
npm install -D tailwindcss postcss autoprefixer
npm install --save headlessui/react formik classnames
#yarn
yarn add -D tailwindcss postcss autoprefixer
yarn add @headlessui/react formik classnames
Initialize tailwind to create the tailwind.config.js and postcss.config.js files:
npx tailwindcss init
Add the .tsx template paths to tailwind.config.js :
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Lastly, modify styles/globals.css to add tailwind directives:
@tailwind base;
@tailwind components;
@tailwind utilities;
Now, we can proceed to create a custom radio button component.
Step 1 - Creating a Custom <RadioGroup/> Component
Let’s create a custom radio group component, that works without Formik. Since the value of a selected item in a radio group can be of any type, let’s create a generic component that can hold any value.
First, create the file components/CustomRadioGroup.tsx and add the following code:
import React from "react";
export type CustomRadioGroupOption<TValue> = {
label: string;
value: TValue;
};
export type CustomRadioGroupProps<TValue> = {
value: TValue;
onChange(newVal: TValue): void;
options: CustomRadioGroupOption<TValue>[];
};
// create a generic component definition that accepts any kind of value
const CustomRadioGroup = <TValue,>(props: CustomRadioGroupProps<TValue>) => {
return <div>CustomRadioGroup</div>;
};
export default CustomRadioGroup;
Now if we use this component provided with CustomRadioGroupOption that has a number value, we get the automatic type-safety:
import CustomRadioGroup from "components/CustomRadioGroup";
import React from "react";
const HomePage = () => {
return (
<CustomRadioGroup
value={null}
onChange={(val) => console.log(val)} // (parameter) val: number
options={[
{
label: "option1",
value: 1,
},
{
label: "option2",
value: 2,
},
]}
></CustomRadioGroup>
);
};
export default HomePage;
This makes sure that we don’t set a value that’s of type string or add an option that has a string value.
Next, let’s make the component functional using the headlessui/RadioGroup component:
import React from "react";
import { RadioGroup } from "@headlessui/react";
import classNames from "classnames";
...
// create a generic component definition that accepts any kind of value
const CustomRadioGroup = <TValue,>(props: CustomRadioGroupProps<TValue>) => {
return (
// use the value and onChange from props.
<RadioGroup value={props.value} onChange={props.onChange}>
<RadioGroup.Label className="text-lg my-2">
{props.label}
</RadioGroup.Label>
{/* render each option. */}
<div className="flex flex-col gap-2">
{props.options.map((option) => {
return (
<RadioGroup.Option value={option.value} key={option.label}>
{/* Use renderProps to get the checked state for each option. */}
{/* Render the state appropriately using tailwind classes */}
{({ checked }) => (
<div
className={classNames({
"flex gap-2 rounded-md px-2 py-1 border-2 cursor-pointer":
true,
"outline outline-1": checked,
})}
>
<span className="w-5 h-5">
{checked ? <span>✔️</span> : <span>⭕</span>}
</span>
<RadioGroup.Label>{option.label}</RadioGroup.Label>
</div>
)}
</RadioGroup.Option>
);
})}
</div>
</RadioGroup>
);
};
It’s totally up to you how you design your <RadioGroup.Option/> component. The code provided above is just an example. If you want to know more about the component, you may read the documentation as they provide examples.
After this step, we have something like this. Mouse and keyboard control just work out of the box:
Step 2 - Creating a Custom FormikRadioGroup Component
Now that the hard part is done, let’s integrate formik by creating a FormikRadioGroup component. We’ll use the combination of useField and setFieldValue to modify the Formik context state.
Create the file components/FormikRadioGroup.tsx :
// components/FormikRadioGroup.tsx
import { useField, useFormikContext } from "formik";
import React from "react";
import CustomRadioGroup, { CustomRadioGroupOption } from "./CustomRadioGroup";
type FormikRadioGroupProps<TValue> = {
name: string;
options: CustomRadioGroupOption<TValue>[];
label: string;
};
const FormikRadioGroup = <TValue,>(props: FormikRadioGroupProps<TValue>) => {
const [field] = useField<TValue>(props.name);
const { setFieldValue } = useFormikContext();
return (
<CustomRadioGroup
options={props.options}
// use field.value for value
value={field.value}
label={props.label}
onChange={(val) => {
// use setFieldValue to modify the formikContext
setFieldValue(props.name, val);
}}
/>
);
};
export default FormikRadioGroup;
Explanation:
Lines 6-11: We define the component and component Props with a generic type. Since CustomRadioGroup is a generic component we should also create a generic component.
Line 18: We set the value of the CustomRadioGroup component using the field .
Line 22: We modify the formik context using setFieldValue .
In my opinion, this is one of the best ways (if not the best) to create custom reusable formik components from regular components.
Step 3 - Usage
To use this component, we should use the <Formik/> provider and have to add the name property to <FormikRadioGroup /> component.
import FormikRadioGroup from "components/FormikRadioGroup";
import { Form, Formik } from "formik";
import React from "react";
const HomePage = () => {
return (
// initialize formik here
<Formik
onSubmit={(values) => console.log(JSON.stringify(values))}
initialValues={{
myoption: null,
}}
>
<Form className="w-fit m-4">
<FormikRadioGroup
name="myoption" //add name props
label="My Radio Group"
options={[
{
label: "option 1",
value: 1,
},
{
label: "option 2",
value: 2,
},
{
label: "option 3",
value: 3,
},
]}
></FormikRadioGroup>
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default HomePage;
When we click submit, the value gets logged in the console:
{"myoption":2}
Full Code + Demo
The full code is publicly available on my GitHub: https://github.com/jmarioste/formik-custom-radio-button-tutorial .
The demo is available on Stackblitz: Formik Custom Radio Group Tutorial
Conclusion
We learned how to integrate Formik with another HeadlessUI and were able to customize the component to meet our requirements. HeadlessUI and Tailwind CSS are both amazing libraries for creating custom and accessible components and I think should be really good to learn more about them.
If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.
Credits: Image by David Mark from Pixabay


