diff --git a/packages/components/src/tailwind/Radio/Radio.stories.tsx b/packages/components/src/tailwind/Radio/Radio.stories.tsx
new file mode 100644
index 00000000000..9e3fd60ab33
--- /dev/null
+++ b/packages/components/src/tailwind/Radio/Radio.stories.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Radio, RadioGroup, type RadioGroupProps } from './Radio';
+
+export default {
+ title: 'Tailwind/Radio',
+ component: Radio,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {},
+};
+
+export const Default = (args: RadioGroupProps) => {
+ return (
+
+
+ Apple: Red, Green, Golden
+ Banana: Ripe, Unripe, Overripe
+ Grapes: Seedless, With seeds, Dried (Raisin)
+ Orange: Navel, Blood, Mandarin
+ Mango: Alphonso, Kesar, Dasheri
+
+
+ );
+};
+
+Default.args = { label: 'Fruits' } as RadioGroupProps;
+
+// Disabled story
+export const Disabled = Default.bind({});
+Disabled.args = {
+ isDisabled: true,
+};
+
+// Invalid story
+export const Invalid = Default.bind({});
+Invalid.args = {
+ isInvalid: true,
+ errorMessage: 'Please select a fruits.',
+};
diff --git a/packages/components/src/tailwind/Radio/Radio.tsx b/packages/components/src/tailwind/Radio/Radio.tsx
new file mode 100644
index 00000000000..d26e2d8b299
--- /dev/null
+++ b/packages/components/src/tailwind/Radio/Radio.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+
+import type {
+ RadioGroupProps as AriaRadioGroupProps,
+ RadioProps as AriaRadioProps,
+ ValidationResult,
+} from 'react-aria-components';
+import {
+ RadioGroup as AriaRadioGroup,
+ Radio as AriaRadio,
+ composeRenderProps,
+} from 'react-aria-components';
+import { tv } from 'tailwind-variants';
+
+import { composeTailwindRenderProps } from '../utils';
+import { Description, FieldError, Label } from '../Field/Field';
+
+interface RadioGroupProps extends Omit {
+ label?: string;
+ children?: React.ReactNode;
+ description?: string;
+ errorMessage?: string | ((validation: ValidationResult) => string);
+ ref?: React.Ref;
+}
+
+const RadioGroup = (props: RadioGroupProps) => {
+ return (
+
+
+ {props.children}
+ {props.description && {props.description}}
+ {props.errorMessage}
+
+ );
+};
+
+const radioStyles = tv({
+ base: 'bg-muted size-4 shrink-0 rounded-full border transition',
+ variants: {
+ isSelected: {
+ false: 'border-input',
+ true: 'border-primary border-[4.5px]',
+ },
+ isFocused: {
+ true: [
+ 'border-ring bg-primary/20 ring-primary/20 ring-4',
+ 'group-invalid:border-danger/70 group-invalid:bg-danger/20 group-invalid:ring-danger/20',
+ ],
+ },
+ isInvalid: {
+ true: 'border-danger/70 bg-danger/20 text-red-700',
+ },
+ isDisabled: {
+ true: 'opacity-50',
+ },
+ },
+});
+
+const radioBaseStyles = tv({
+ base: 'group flex items-center gap-2 text-sm transition [&[data-disabled]]:opacity-50',
+});
+
+interface RadioProps extends AriaRadioProps {
+ description?: string;
+ label?: string;
+ ref?: React.Ref;
+}
+
+const Radio = (props: RadioProps) => {
+ return (
+
+ radioBaseStyles({ ...renderProps, className }),
+ )}
+ >
+ {(renderProps) => (
+
+
+
+ {props.label || props.description ? (
+ <>
+ {}
+ {props.description && (
+ {props.description}
+ )}
+ >
+ ) : (
+ (props.children as React.ReactNode)
+ )}
+
+
+ )}
+
+ );
+};
+
+export type { RadioGroupProps, RadioProps };
+export { Radio, RadioGroup };