RadioGroup

Radio group for selecting a single option from a list

Import

import { RadioGroup, Radio } from "@thenamespace/uikit";

Usage

Plan selectionChoose the plan that suits you best
Includes 100 messages per month
Includes 200 messages per month
Unlimited messages
import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";

export function Basic() {
  return (
    <RadioGroup defaultValue="premium" name="plan">

Anatomy

Import the RadioGroup component and access all parts using dot notation.

import {
  RadioGroup,
  Radio,
  Label,
  Description,
  FieldError,
} from "@thenamespace/uikit";

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

Plan selectionChoose the plan that suits you best
Includes 100 messages per month
Includes 200 messages per month
Unlimited messages
"use client";

import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";

export function CustomIndicator() {

Horizontal Orientation

For side projects
Advanced reporting
Up to 10 teammates
import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";

export function Horizontal() {
  return (
    <div className="flex flex-col gap-4">

Controlled

Subscription plan
For side projects and small teams
Advanced reporting and analytics
Share access with up to 10 teammates

Selected plan: pro

"use client";

import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";
import React from "react";

Uncontrolled

Combine defaultValue with onChange when you only need to react to updates.

Subscription plan
For side projects and small teams
Advanced reporting and analytics
Share access with up to 10 teammates

Last chosen plan: pro

"use client";

import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";
import React from "react";

Validation

Subscription plan
For side projects and small teams
Advanced reporting and analytics
Share access with up to 10 teammates
"use client";

import {Button, Description, FieldError, Form, Label, Radio, RadioGroup} from "@thenamespace/uikit";
import React from "react";

Disabled

Subscription planPlan changes are temporarily paused while we roll out updates.
For side projects and small teams
Advanced reporting and analytics
Share access with up to 10 teammates
import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";

export function Disabled() {
  return (
    <RadioGroup isDisabled defaultValue="pro" name="plan-disabled">

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

Primary variant

Standard styling with default background
Another option with primary styling

Secondary variant

Lower emphasis variant for use in surfaces
Another option with secondary styling
import {Description, Radio, RadioGroup} from "@thenamespace/uikit";

export function Variants() {
  return (
    <div className="flex flex-col gap-8">

In Surface

When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.

Plan selectionChoose the plan that suits you best
Includes 100 messages per month
Includes 200 messages per month
Unlimited messages
import {Description, Label, Radio, RadioGroup, Surface} from "@thenamespace/uikit";

export function OnSurface() {
  return (
    <Surface className="w-full rounded-3xl p-6">

Delivery & Payment

Delivery method
Payment method
import { Description, Label, Radio, RadioGroup } from "@thenamespace/uikit";
import clsx from "clsx";

import { Icon } from "@/demos/icon";

Custom Render Function

Plan selectionChoose the plan that suits you best
Includes 100 messages per month
Includes 200 messages per month
Unlimited messages
"use client";

import {Description, Label, Radio, RadioGroup} from "@thenamespace/uikit";

export function CustomRenderFunction() {

Styling

Passing Tailwind CSS classes

import { RadioGroup, Radio } from "@thenamespace/uikit";

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;
  }
}

Namespace UIKit 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:

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: :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 Props

PropTypeDefaultDescription
valuestring-The current value (controlled)
defaultValuestring-The default value (uncontrolled)
onChange(value: string) => void-Handler called when the value changes
isDisabledbooleanfalseWhether the radio group is disabled
isRequiredbooleanfalseWhether the radio group is required
isReadOnlybooleanfalseWhether the radio group is read only
isInvalidbooleanfalseWhether 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.
namestring-The name of the radio group, used when submitting an HTML form
orientation'horizontal' | 'vertical''vertical'The orientation of the radio group
childrenReact.ReactNode | (values: RadioGroupRenderProps) => React.ReactNode-Radio group content or render prop
renderDOMRenderFunction<keyof React.JSX.IntrinsicElements, RadioGroupRenderProps>-Overrides the default DOM element with a custom render function.

Radio Props

PropTypeDefaultDescription
valuestring-The value of the radio button
isDisabledbooleanfalseWhether the radio button is disabled
namestring-The name of the radio button, used when submitting an HTML form
childrenReact.ReactNode | (values: RadioFieldRenderProps) => React.ReactNode-Radio content or field render prop
renderDOMRenderFunction<keyof React.JSX.IntrinsicElements, RadioFieldRenderProps>-Overrides the default DOM element with a custom render function.

Radio.Control Props

Extends React.HTMLAttributes<HTMLSpanElement>.

PropTypeDefaultDescription
childrenReact.ReactNode-The content to render inside the control wrapper (typically Radio.Indicator)

Radio.Indicator Props

Extends React.HTMLAttributes<HTMLSpanElement>.

PropTypeDefaultDescription
childrenReact.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.

PropTypeDefaultDescription
childrenReact.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:

PropTypeDescription
isSelectedbooleanWhether the radio is currently selected
isDisabledbooleanWhether the radio is disabled
isReadOnlybooleanWhether the radio is read only
isInvalidbooleanWhether the radio is in an invalid state
isRequiredbooleanWhether 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.