RadioGroupUpdated
Radio group for selecting a single option from a list
Import
import { RadioGroup, Radio } from '@heroui/react';Usage
Anatomy
Import the RadioGroup component and access all parts using dot notation.
import {RadioGroup, Radio, Label, Description, FieldError} from '@heroui/react';
export default () => (
<RadioGroup>
<Label />
<Description />
<Radio value="option1">
<Radio.Content> {/* The clickable area: control + label */}
<Radio.Control>
<Radio.Indicator>
<span>✓</span> {/* Custom indicator (optional) */}
</Radio.Indicator>
</Radio.Control>
Label {/* plain text — the clickable label */}
</Radio.Content>
<Description /> {/* Sibling — stays outside the button (announced via aria-describedby) */}
<FieldError /> {/* Optional — per-radio validation message */}
</Radio>
<FieldError /> {/* Optional — group-level validation */}
</RadioGroup>
)Custom Indicator
Horizontal Orientation
Controlled
Uncontrolled
Combine defaultValue with onChange when you only need to react to updates.
Validation
Disabled
Variants
The RadioGroup component supports two visual variants:
primary(default) - Standard styling with default background, suitable for most use casessecondary- Lower emphasis variant, suitable for use in Surface components
In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
Delivery & Payment
Custom Render Function
Styling
Passing Tailwind CSS classes
import { RadioGroup, Radio } from '@heroui/react';
export default () => (
<RadioGroup defaultValue="premium" name="plan">
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-blue-300 data-[selected=true]:border-blue-500 data-[selected=true]:bg-blue-500/10"
value="basic"
>
<Radio.Indicator className="border-border border-2 group-hover:border-blue-400 group-data-[selected=true]:border-blue-500 group-data-[selected=true]:bg-blue-500" />
Basic Plan
</Radio>
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-purple-300 data-[selected=true]:border-purple-500 data-[selected=true]:bg-purple-500/10"
value="premium"
>
<Radio.Indicator className="border-border border-2 group-hover:border-purple-400 group-data-[selected=true]:border-purple-500 group-data-[selected=true]:bg-purple-500" />
Premium Plan
</Radio>
<Radio
className="border-border group cursor-pointer rounded-xl border-2 p-4 hover:border-emerald-300 data-[selected=true]:border-emerald-500 data-[selected=true]:bg-emerald-500/10"
value="business"
>
<Radio.Indicator className="border-border border-2 group-hover:border-emerald-400 group-data-[selected=true]:border-emerald-500 group-data-[selected=true]:bg-emerald-500" />
Business Plan
</Radio>
</RadioGroup>
);Customizing the component classes
To customize the RadioGroup component classes, you can use the @layer components directive.
Learn more.
@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-primary;
}
.radio__indicator {
@apply bg-primary;
}
.radio__content {
@apply gap-1;
}
}HeroUI follows the BEM 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):
Base Classes
.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).radio__content- Radio content wrapper
Modifier Classes
.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:
:hoveror[data-hovered="true"](border color changes) - Focus:
:focus-visibleor[data-focus-visible="true"](shows focus ring) - Pressed:
:activeor[data-pressed="true"](scale transform) - Disabled:
:disabledor[aria-disabled="true"](reduced opacity, no pointer events) - Invalid:
[data-invalid="true"]or[aria-invalid="true"](error border color)
API Reference
RadioGroup Props
| 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<keyof React.JSX.IntrinsicElements, RadioGroupRenderProps> | - | Overrides the default DOM element with a custom render function. |
Radio Props
| 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<keyof React.JSX.IntrinsicElements, RadioFieldRenderProps> | - | Overrides the default DOM element with a custom render function. |
Radio.Control Props
Extends React.HTMLAttributes<HTMLSpanElement>.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | The content to render inside the control wrapper (typically Radio.Indicator) |
Radio.Indicator Props
Extends React.HTMLAttributes<HTMLSpanElement>.
| 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 Props
The clickable area of the radio (the <label> wrapping the hidden input). Place Radio.Control and the Label inside it. className accepts a render function that receives RadioButtonRenderProps.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | (values: RadioButtonRenderProps) => React.ReactNode | - | The clickable content (typically Radio.Control and Label) |
RadioFieldRenderProps
When using a render prop on the root Radio, these field-level values are provided:
| Prop | Type | Description |
|---|---|---|
isSelected | boolean | Whether the radio is currently selected |
isDisabled | boolean | Whether the radio is disabled |
isReadOnly | boolean | Whether the radio is read only |
isInvalid | boolean | Whether the radio is in an invalid state |
isRequired | boolean | Whether the radio is required |
RadioButtonRenderProps
Radio.Control and Radio.Indicator use button-level render props (isHovered, isPressed, isFocusVisible, etc.). Pass a function as Radio.Control children or to Radio.Indicator to access them.





