Skip to content

Radio ​

A flexible radio button component for single selections from multiple options. Supports labels, descriptions, validation states, and full reactivity. Built for Pulse Framework with complete accessibility support.

Import ​

tsx
import { Radio, Pulse } from '@odyssee-software/components';

Basic Usage ​

Code Éditable
Résultat

With Descriptions ​

Add helpful descriptions below each option.

Code Éditable
Résultat

Sizes ​

Five size options control the radio button size: xs, sm, md, lg, and xl.

Code Éditable
Résultat

Required Field ​

Mark radio groups as required with visual indicators.

Code Éditable
Résultat

With Error State ​

Display validation errors on radio options.

Code Éditable
Résultat

With Success State ​

Show success feedback on selected options.

Code Éditable
Résultat

Disabled State ​

Disable radio buttons when they cannot be interacted with.

Code Éditable
Résultat

Label Position ​

Control label placement with the labelPosition prop.

Code Éditable
Résultat

Reactive Value ​

Control radio selection with Pulse signals.

tsx
const selectedPlan = Pulse.signal('free');

const reactiveRadioGroup = (
  <div>
    <div class="space-y-2">
      <Radio
        name="plan"
        value="free"
        label="Free Plan"
        checked={Pulse.computed(() => selectedPlan() === 'free')}
        onChange={(value) => selectedPlan(value)}
      />
      <Radio
        name="plan"
        value="pro"
        label="Pro Plan"
        checked={Pulse.computed(() => selectedPlan() === 'pro')}
        onChange={(value) => selectedPlan(value)}
      />
      <Radio
        name="plan"
        value="enterprise"
        label="Enterprise Plan"
        checked={Pulse.computed(() => selectedPlan() === 'enterprise')}
        onChange={(value) => selectedPlan(value)}
      />
    </div>
    
    <div class="mt-4">
      <p class="text-sm text-gray-600">Selected: {selectedPlan()}</p>
    </div>
  </div>
);

Radio Group Pattern ​

Create organized radio button groups.

tsx
const RadioGroupExample = () => {
  const selectedOption = Pulse.signal('option1');

  const options = [
    { value: 'option1', label: 'Option 1', description: 'First choice' },
    { value: 'option2', label: 'Option 2', description: 'Second choice' },
    { value: 'option3', label: 'Option 3', description: 'Third choice' }
  ];

  return (
    <div class="space-y-3">
      <h3 class="text-lg font-semibold">Choose an option</h3>
      
      {options.map(option => (
        <Radio
          key={option.value}
          name="radio-group"
          value={option.value}
          label={option.label}
          description={option.description}
          checked={Pulse.computed(() => selectedOption() === option.value)}
          onChange={(value) => selectedOption(value)}
        />
      ))}
    </div>
  );
};

Card-Style Radio Buttons ​

Create visually rich radio button cards.

tsx
const CardRadioButtons = () => {
  const selectedPlan = Pulse.signal('pro');

  const plans = [
    {
      value: 'free',
      label: 'Free',
      price: '$0',
      features: ['5 projects', '1 GB storage', 'Basic support']
    },
    {
      value: 'pro',
      label: 'Pro',
      price: '$29',
      features: ['Unlimited projects', '100 GB storage', 'Priority support']
    },
    {
      value: 'enterprise',
      label: 'Enterprise',
      price: '$99',
      features: ['Custom solutions', 'Unlimited storage', '24/7 support']
    }
  ];

  return (
    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
      {plans.map(plan => (
        <label
          class={`
            relative border rounded-lg p-6 cursor-pointer transition-all
            ${selectedPlan() === plan.value 
              ? 'border-blue-500 ring-2 ring-blue-500 bg-blue-50' 
              : 'border-gray-200 hover:border-blue-300'}
          `}
        >
          <Radio
            name="plan-cards"
            value={plan.value}
            label=""
            checked={Pulse.computed(() => selectedPlan() === plan.value)}
            onChange={(value) => selectedPlan(value)}
            className="absolute top-4 right-4"
          />
          
          <div>
            <h3 class="text-xl font-bold mb-2">{plan.label}</h3>
            <p class="text-3xl font-bold text-blue-600 mb-4">
              {plan.price}<span class="text-sm text-gray-500">/mo</span>
            </p>
            <ul class="space-y-2">
              {plan.features.map(feature => (
                <li class="text-sm text-gray-600">✓ {feature}</li>
              ))}
            </ul>
          </div>
        </label>
      ))}
    </div>
  );
};

Form Validation ​

Integrate with form validation.

tsx
const PreferenceForm = () => {
  const preference = Pulse.signal('');
  const error = Pulse.signal('');

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    
    if (!preference()) {
      error('Please select a preference');
      return;
    }
    
    error('');
    console.log('Form submitted with:', preference());
  };

  return (
    <form onsubmit={handleSubmit} class="space-y-4">
      <div class="space-y-2">
        <label class="text-sm font-semibold">
          Contact Preference <span class="text-red-500">*</span>
        </label>
        
        <Radio
          name="contact"
          value="email"
          label="Email"
          description="We'll contact you via email"
          checked={Pulse.computed(() => preference() === 'email')}
          onChange={(value) => {
            preference(value);
            error('');
          }}
        />
        
        <Radio
          name="contact"
          value="phone"
          label="Phone"
          description="We'll call you directly"
          checked={Pulse.computed(() => preference() === 'phone')}
          onChange={(value) => {
            preference(value);
            error('');
          }}
        />
        
        <Radio
          name="contact"
          value="sms"
          label="SMS"
          description="We'll send you a text message"
          checked={Pulse.computed(() => preference() === 'sms')}
          onChange={(value) => {
            preference(value);
            error('');
          }}
        />
        
        {error() && (
          <p class="text-sm text-red-600 mt-2">{error()}</p>
        )}
      </div>
      
      <Button type="submit">Submit</Button>
    </form>
  );
};

Props ​

PropTypeDefaultDescription
namestringRequiredRadio group name
valuestring | numberRequiredRadio button value
checkedboolean | Signal<boolean>falseChecked state
labelstring | HTMLElement-Radio label
descriptionstring-Helper text below label
errorstring-Error message
successstring-Success message
size"xs" | "sm" | "md" | "lg" | "xl""md"Radio size
labelPosition"left" | "right""right"Label position
disabledbooleanfalseDisable radio
requiredbooleanfalseMark as required
onChange(value: string) => void-Change event handler
classNamestring-Additional CSS classes
idstringAuto-generatedHTML id attribute

Accessibility ​

The Radio component follows accessibility best practices:

  • ✅ Proper label association with for attribute
  • ✅ ARIA attributes for states
  • ✅ Keyboard navigation support (Arrow keys, Space, Tab)
  • ✅ Focus management
  • ✅ Screen reader friendly
  • ✅ Required field indicators
  • ✅ Group semantics with same name attribute

Keyboard Navigation ​

  • Arrow Up/Down: Navigate between radio buttons in the same group
  • Space: Select focused radio button
  • Tab: Move to next form element

ARIA Attributes ​

tsx
const accessibleRadio = (
  <Radio
    name="option"
    value="1"
    label="Option 1"
    aria-label="Select option 1"
    aria-describedby="option-hint"
  />
);

Best Practices ​

✅ Do ​

  • Use radio buttons for mutually exclusive options
  • Provide clear, distinct labels
  • Add descriptions for complex options
  • Group related options with the same name
  • Show validation feedback
  • Use 2-5 options (use Select for more)
tsx
// Good: Clear options with descriptions
const goodRadioGroup = (
  <div class="space-y-3">
    <Radio
      name="subscription"
      value="monthly"
      label="Monthly Billing"
      description="Pay $29 every month, cancel anytime"
    />
    <Radio
      name="subscription"
      value="yearly"
      label="Annual Billing"
      description="Pay $290/year, save 17%"
    />
  </div>
);

❌ Don't ​

  • Don't use for multiple selections (use checkboxes)
  • Don't use for on/off options (use toggle)
  • Don't use too many options (use select/dropdown)
  • Don't forget to set the name attribute
  • Don't use radio buttons without labels
tsx
// Bad: Using radio for multiple selections
const badUsage = (
  <div>
    <Radio name="features" value="feature1" label="Feature 1" />
    <Radio name="features" value="feature2" label="Feature 2" />
    {/* User can't select multiple - should use checkboxes */}
  </div>
);

// Bad: Too many options
const tooManyOptions = (
  <div>
    {countries.map(country => (
      <Radio name="country" value={country.code} label={country.name} />
    ))}
    {/* Should use Select dropdown instead */}
  </div>
);

Use Cases ​

Subscription Plans ​

tsx
const SubscriptionSelector = () => (
  <div class="space-y-3">
    <Radio
      name="plan"
      value="free"
      label="Free Plan"
      description="Perfect for individuals"
    />
    <Radio
      name="plan"
      value="pro"
      label="Pro Plan"
      description="$29/month - For professionals"
    />
  </div>
);

Delivery Options ​

tsx
const DeliveryOptions = () => (
  <div class="space-y-2">
    <Radio name="delivery" value="pickup" label="Store Pickup" />
    <Radio name="delivery" value="standard" label="Standard Delivery" />
    <Radio name="delivery" value="express" label="Express Delivery" />
  </div>
);

Survey Questions ​

tsx
const SurveyQuestion = () => (
  <div class="space-y-2">
    <p class="font-semibold mb-3">How satisfied are you?</p>
    <Radio name="satisfaction" value="1" label="Very Dissatisfied" />
    <Radio name="satisfaction" value="2" label="Dissatisfied" />
    <Radio name="satisfaction" value="3" label="Neutral" />
    <Radio name="satisfaction" value="4" label="Satisfied" />
    <Radio name="satisfaction" value="5" label="Very Satisfied" />
  </div>
);

Styling & Theming ​

All radio styles use Tailwind CSS classes and support dark mode automatically.

Custom Styling ​

tsx
const customRadio = (
  <Radio
    name="custom"
    value="1"
    label="Custom styled radio"
    className="accent-purple-600"
  />
);

TypeScript ​

Full TypeScript support with complete type definitions:

tsx
import type { RadioProps } from '@odyssee-software/components';

const props: RadioProps = {
  name: 'option',
  value: '1',
  label: 'Option 1',
  checked: false,
  onChange: (value: string) => {
    console.log('Selected:', value);
  }
};

const radio = <Radio {...props} />;

Version: 1.0.0
Last Updated: January 2025

Released under the MIT License.