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: [], }
123456789101112
Lastly, modify styles/globals.css
to add tailwind directives:
@tailwind base; @tailwind components; @tailwind utilities;
123
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;
123456789101112131415
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;
123456789101112131415161718192021
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> ); };
12345678910111213141516171819202122232425262728293031323334353637383940
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;
1234567891011121314151617181920212223242526
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;
12345678910111213141516171819202122232425262728293031323334353637
When we click submit, the value gets logged in the console:
{"myoption":2}
1
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