MC Radio Group

A set of radio buttons for selecting one option from a group.

Overview

The mc-radio-group is a core form control of the MicroClub UI library. Built on the @base-ui/react radio group primitive, it provides accessible state handling and keyboard interactions while keeping MicroClub styling and theme tokens.

As a part of the MicroClub library, this component is designed for instant deployment into your codebase, giving you full ownership of its look and behavior.

Preview

Loading...
import { McRadioGroup, McRadioGroupItem } from '../ui/mc-radio-group';export default function RadioGroupDemo() {  return (    <McRadioGroup defaultValue="remember" className="w-fit">      <McRadioGroupItem        value="remember"        id="r1"        text="Remember me"        supportText="Save my login details for next time."      />    </McRadioGroup>  );}

Add to Your Project

Deploy the mc-radio-group directly to your components/ui directory:

npx mcoli-ui@latest add mc-radio-group
pnpm dlx mcoli-ui@latest add mc-radio-group
yarn dlx mcoli-ui@latest add mc-radio-group
bunx mcoli-ui@latest add mc-radio-group

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install @base-ui/react
pnpm add @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react
Copy the source code
'use client';import * as React from 'react';import { Radio as RadioPrimitive } from '@base-ui/react/radio';import { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';import { cva, type VariantProps } from 'class-variance-authority';import { cn } from '@/lib/utils';const radioGroupVariants = cva(  'peer relative flex shrink-0 items-center justify-center rounded-full border transition-colors outline-none disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50',  {    variants: {      size: {        sm: 'size-4 [&_[data-slot=radio-group-indicator]>span]:size-2',        md: 'size-5 [&_[data-slot=radio-group-indicator]>span]:size-2.5',      },    },    defaultVariants: {      size: 'sm',    },  });const radioGroupItemVariants = cva('inline-flex', {  variants: {    size: {      sm: 'gap-2',      md: 'gap-3',    },  },  defaultVariants: {    size: 'sm',  },});const textVariants = cva('font-medium transition-colors', {  variants: {    size: {      sm: 'text-sm',      md: 'text-base',    },    disabled: {      true: 'text-muted-foreground',      false: 'text-foreground',    },  },  defaultVariants: {    size: 'sm',    disabled: false,  },});const supportTextVariants = cva('font-regular', {  variants: {    size: {      sm: 'text-sm',      md: 'text-base',    },    disabled: {      true: 'text-muted-foreground',      false: 'text-foreground',    },  },  defaultVariants: {    size: 'sm',    disabled: false,  },});function McRadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {  return (    <RadioGroupPrimitive      data-slot="radio-group"      className={cn('grid w-full gap-2', className)}      {...props}    />  );}export interface McRadioGroupItemProps  extends RadioPrimitive.Root.Props, VariantProps<typeof radioGroupVariants> {  text?: string;  supportText?: string;}function McRadioGroupItem({  className,  size = 'sm',  text,  supportText,  id,  disabled,  value,  ...props}: McRadioGroupItemProps) {  const radioId = React.useId();  const finalId = id ?? radioId;  const labelId = text ? `${finalId}-label` : undefined;  const descriptionId = supportText ? `${finalId}-description` : undefined;  return (    <label      htmlFor={finalId}      className={cn(        radioGroupItemVariants({ size }),        supportText ? 'items-start' : 'items-center',        disabled ? 'cursor-not-allowed' : 'cursor-pointer',        className      )}    >      <RadioPrimitive.Root        id={finalId}        data-slot="radio-group-item"        className={cn(          radioGroupVariants({ size }),          'self-start mt-0.5',          'bg-primary-foreground border-muted-foreground',          'hover:border-primary',          'focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring',          'focus-within:outline-none focus-within:ring-4 focus-within:ring-ring',          'data-disabled:border-muted-foreground data-disabled:hover:border-muted-foreground',          'data-checked:border-primary'        )}        disabled={disabled}        value={value}        aria-labelledby={labelId}        aria-describedby={descriptionId}        {...props}      >        <RadioPrimitive.Indicator          data-slot="radio-group-indicator"          className="flex size-4 items-center justify-center"        >          <span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary" />        </RadioPrimitive.Indicator>      </RadioPrimitive.Root>      {(text || supportText) && (        <span className={cn('flex flex-col', size === 'sm' ? 'gap-0' : 'gap-0.5')}>          {text && (            <span id={labelId} className={textVariants({ size, disabled: !!disabled })}>              {text}            </span>          )}          {supportText && (            <span              id={descriptionId}              className={supportTextVariants({ size, disabled: !!disabled })}            >              {supportText}            </span>          )}        </span>      )}    </label>  );}export { McRadioGroup, McRadioGroupItem };
Adjust import paths

Ensure your @/lib/utils or equivalent classname helper is correctly imported.

Usage

import { McRadioGroup, McRadioGroupItem } from '@/components/ui/mc-radio-group';

export default function Example() {
  return (
    <McRadioGroup defaultValue="remember">
      <McRadioGroupItem
        value="remember"
        id="r1"
        text="Remember me"
        supportText="Save my login details for next time."
      />
    </McRadioGroup>
  );
}

Examples

Sizes

<McRadioGroup defaultValue="sm">
  <McRadioGroupItem value="sm" id="s1" size="sm" text="Remember me" />
  <McRadioGroupItem value="md" id="s2" size="md" text="Remember me" />
</McRadioGroup>

With Support Text

<McRadioGroup defaultValue="option1">
  <McRadioGroupItem
    value="option1"
    id="r1"
    text="Remember me"
    supportText="Save my login details for next time."
  />
  <McRadioGroupItem
    value="option2"
    id="r2"
    text="Remember me"
    supportText="Save my login details for next time."
  />
</McRadioGroup>

States

<McRadioGroup defaultValue="checked">
  <McRadioGroupItem value="unchecked" id="r1" text="Unchecked" />
  <McRadioGroupItem value="checked" id="r2" text="Checked" />
  <McRadioGroupItem value="disabled" id="r3" text="Disabled" disabled />
  <McRadioGroupItem value="disabled-checked" id="r4" text="Disabled checked" disabled />
</McRadioGroup>

Controlled

Manage the selected value programmatically with value and onValueChange.

import { useState } from 'react';
import { McRadioGroup, McRadioGroupItem } from '@/components/ui/mc-radio-group';

export default function Example() {
  const [value, setValue] = useState('option1');

  return (
    <McRadioGroup value={value} onValueChange={setValue}>
      <McRadioGroupItem value="option1" id="r1" text="Option 1" />
      <McRadioGroupItem value="option2" id="r2" text="Option 2" />
      <McRadioGroupItem value="option3" id="r3" text="Option 3" />
    </McRadioGroup>
  );
}

API Reference

McRadioGroup Props

PropTypeDefaultDescription
valuestringundefinedThe controlled selected value
onValueChange(value: string) => voidundefinedCallback when selection changes
defaultValuestringundefinedThe initial selected value
disabledbooleanfalseDisables all radio items in the group
classNamestringundefinedAdditional CSS classes for the group

McRadioGroupItem Props

PropTypeDefaultDescription
valuestringundefinedThe value associated with this radio item
size"sm" | "md""sm"The radio button size
textstringundefinedMain label displayed next to the radio button
supportTextstringundefinedSecondary helper text under the main label
disabledbooleanfalseDisables interactions and applies disabled styles
idstringundefinedCustom ID for the radio input
classNamestringundefinedAdditional CSS classes for the radio root

All other props from @base-ui/react/radio are supported.

On this page