# RadioGroup **Category**: react **URL**: https://heroui.com/en/docs/react/components/radio-group **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/radio-group.mdx > Radio group for selecting a single option from a list *** ## Usage ```tsx import { RadioGroup, Radio } from '@heroui/react'; ``` ```tsx import {Description, Label, Radio, RadioGroup} from "@heroui/react"; export function Basic() { return ( Choose the plan that suits you best Basic Plan Includes 100 messages per month Premium Plan Includes 200 messages per month Business Plan Unlimited messages ); } ``` ## Anatomy ```tsx import {RadioGroup, Radio, Label, Description, FieldError} from '@heroui/react'; export default () => ( ) ``` ## Examples ### Horizontal Orientation ```tsx import {Description, Label, Radio, RadioGroup} from "@heroui/react"; export function Horizontal() { return (
Starter For side projects Pro Advanced reporting Teams Up to 10 teammates
); } ``` ### Variants The RadioGroup component supports two visual variants: - **`primary`** (default) - Standard styling with default background, suitable for most use cases - **`secondary`** - Lower emphasis variant, suitable for use in Surface components ```tsx import {Description, Radio, RadioGroup} from "@heroui/react"; export function Variants() { return (

Primary variant

Option 1 Standard styling with default background Option 2 Another option with primary styling

Secondary variant

Option 1 Lower emphasis variant for use in surfaces Option 2 Another option with secondary styling
); } ``` ### In Surface When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds. ```tsx import {Description, Label, Radio, RadioGroup, Surface} from "@heroui/react"; export function OnSurface() { return ( Choose the plan that suits you best Basic Plan Includes 100 messages per month Premium Plan Includes 200 messages per month Business Plan Unlimited messages ); } ``` ### Disabled ```tsx import {Description, Label, Radio, RadioGroup} from "@heroui/react"; export function Disabled() { return ( Plan changes are temporarily paused while we roll out updates. Starter For side projects and small teams Pro Advanced reporting and analytics Teams Share access with up to 10 teammates ); } ``` ### Controlled ```tsx "use client"; import {Description, Label, Radio, RadioGroup} from "@heroui/react"; import React from "react"; export function Controlled() { const [value, setValue] = React.useState("pro"); return (
Starter For side projects and small teams Pro Advanced reporting and analytics Teams Share access with up to 10 teammates

Selected plan: {value}

); } ``` ### Uncontrolled Combine `defaultValue` with `onChange` when you only need to react to updates. ```tsx "use client"; import {Description, Label, Radio, RadioGroup} from "@heroui/react"; import React from "react"; export function Uncontrolled() { const [selection, setSelection] = React.useState("pro"); return (
setSelection(nextValue)} > Starter For side projects and small teams Pro Advanced reporting and analytics Teams Share access with up to 10 teammates

Last chosen plan: {selection}

); } ``` ### Validation ```tsx "use client"; import {Button, Description, FieldError, Form, Label, Radio, RadioGroup} from "@heroui/react"; import React from "react"; export function Validation() { const [message, setMessage] = React.useState(null); return (
{ e.preventDefault(); const formData = new FormData(e.currentTarget); const value = formData.get("plan-validation"); setMessage(`Your chosen plan is: ${value}`); }} > Starter For side projects and small teams Pro Advanced reporting and analytics Teams Share access with up to 10 teammates Choose a subscription before continuing. {!!message &&

{message}

}
); } ``` ### Delivery & Payment ### Custom Indicator ```tsx "use client"; import {Description, Label, Radio, RadioGroup} from "@heroui/react"; export function CustomIndicator() { return ( Choose the plan that suits you best {({isSelected}) => isSelected ? : null } Basic Plan Includes 100 messages per month {({isSelected}) => isSelected ? : null } Premium Plan Includes 200 messages per month {({isSelected}) => isSelected ? : null } Business Plan Unlimited messages ); } ``` ### Render Function ```tsx "use client"; import {Description, Label, Radio, RadioGroup} from "@heroui/react"; export function RenderFunction() { return (
} > Choose the plan that suits you best Basic Plan Includes 100 messages per month Premium Plan Includes 200 messages per month Business Plan Unlimited messages ); } ``` ## Customization ### Tailwind CSS ```tsx import {Description, Label, Radio, RadioGroup} from "@heroui/react"; const options = [ {description: "$12 billed every month", label: "Monthly", value: "monthly"}, {description: "$120 billed once a year", label: "Yearly", value: "yearly"}, ] as const; const contentClassName = "group flex w-full items-start gap-3 rounded-xl border border-success/10 bg-success-soft/30 px-4 py-3 transition-colors data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-success/15 data-[hovered=true]:bg-success-soft-hover data-[selected=true]:border-success/30 data-[selected=true]:bg-success-soft data-[selected=true]:data-[hovered=true]:bg-success-soft-hover"; const controlClassName = "mt-0.5 size-5 shrink-0 rounded-full border border-border bg-default shadow-none group-data-[pressed=true]:scale-95 group-data-[selected=true]:border-transparent group-data-[selected=true]:bg-success group-data-[selected=true]:group-data-[pressed=true]:bg-success-hover"; const indicatorClassName = "before:rounded-full before:bg-default group-data-[selected=true]:before:scale-50 group-data-[selected=true]:before:bg-success-foreground group-data-[selected=true]:group-data-[pressed=true]:before:scale-[0.57]"; export function CustomStyles() { return ( Choose how often you are charged. {options.map(({description, label, value}) => (
{label} {description}
))}
); } ``` ### Global CSS To customize the RadioGroup component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .radio-group { @apply gap-2; } .radio { @apply gap-4 rounded-lg border border-border p-3 hover:bg-surface-hovered; } .radio__control { @apply border-2 border-accent; } .radio__indicator { @apply bg-primary; } .radio__content { @apply gap-1; } } ``` ## Styling Reference HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The RadioGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/radio-group.css)): #### Base Classes [!toc] - `.radio-group` - Base radio group container - `.radio` - Individual radio item - `.radio__content` - Radio button (RAC label wrapper for control + label text) - `.radio__control` - Radio control (circular button) - `.radio__indicator` - Radio indicator (inner dot) #### Modifier Classes [!toc] - `.radio--disabled` - Disabled radio state ### Interactive States The radio supports both CSS pseudo-classes and data attributes for flexibility: - **Selected**: `[aria-checked="true"]` or `[data-selected="true"]` (indicator appears) - **Hover**: `:hover` or `[data-hovered="true"]` (border color changes) - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring) - **Pressed**: `:active` or `[data-pressed="true"]` (scale transform) - **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events) - **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` (error border color) ## API Reference ### RadioGroup | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `string` | - | The current value (controlled) | | `defaultValue` | `string` | - | The default value (uncontrolled) | | `onChange` | `(value: string) => void` | - | Handler called when the value changes | | `isDisabled` | `boolean` | `false` | Whether the radio group is disabled | | `isRequired` | `boolean` | `false` | Whether the radio group is required | | `isReadOnly` | `boolean` | `false` | Whether the radio group is read only | | `isInvalid` | `boolean` | `false` | Whether the radio group is in an invalid state | | `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. | | `name` | `string` | - | The name of the radio group, used when submitting an HTML form | | `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | The orientation of the radio group | | `children` | `React.ReactNode \| (values: RadioGroupRenderProps) => React.ReactNode` | - | Radio group content or render prop | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function.| ### Radio | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `string` | - | The value of the radio button | | `isDisabled` | `boolean` | `false` | Whether the radio button is disabled | | `name` | `string` | - | The name of the radio button, used when submitting an HTML form | | `children` | `React.ReactNode \| (values: RadioFieldRenderProps) => React.ReactNode` | - | Radio content or field render prop | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function.| ### Radio.Control Extends `React.HTMLAttributes`. | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `React.ReactNode` | - | The content to render inside the control wrapper (typically Radio.Indicator) | ### Radio.Indicator Extends `React.HTMLAttributes`. | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `React.ReactNode \| (values: RadioButtonRenderProps) => React.ReactNode` | - | Optional content or render prop that receives the current radio button state. | ### Radio.Content The clickable area of the radio (the `