Skip to content
Last update: June 24, 2025

VcRadioButton Component

The VcRadioButton component is a molecule used throughout the VC-Shell framework for creating radio button inputs. It allows users to select a single option from a set of related choices, supporting various states and customizations.

Storybook

VcRadioButton Storybook

Basic Usage

<template>
  <div>
    <VcRadioButton
      v-model="selectedOption"
      value="option1"
      label="Option 1"
    />
    <VcRadioButton
      v-model="selectedOption"
      value="option2"
      label="Option 2"
    />
    <VcRadioButton
      v-model="selectedOption"
      value="option3"
      label="Option 3"
    />
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton } from '@vc-shell/framework';

const selectedOption = ref('option1');
</script>

Props

Prop Type Default Description
modelValue any undefined Current selected value (v-model)
value any undefined Value of this radio button
binary boolean false Whether to use as a boolean toggle
checked boolean false Whether the radio button is checked
disabled boolean false Whether the radio button is disabled
name string 'RadioField' Name attribute for the input element
label string undefined Label text displayed next to the radio button
error boolean false Whether to show the component in error state
errorMessage string undefined Error message to display below the radio button

Events

Event Payload Description
update:modelValue any Emitted when the radio button state changes

Slots

Slot Description
error Custom content for error message display

CSS Variables

The radio button component uses CSS variables for theming, which can be customized:

:root {
  --radio-active: var(--primary-500);            /* Color of the radio button when active/checked */
  --radio-active-inner: var(--additional-50);    /* Color of the inner circle when checked */
  --radio-border: var(--neutrals-400);           /* Border color of the radio button in normal state */
  --radio-background: var(--additional-50);      /* Background color of the unchecked radio button */
  --radio-disabled: var(--secondary-100);        /* Background color when disabled */
  --radio-disabled-inner: var(--secondary-200);  /* Inner color when disabled and checked */
  --radio-error: var(--danger-500);              /* Border color when in error state */
  --radio-size: 20px;                            /* Size of the radio button */
  --radio-border-outline: var(--primary-50);     /* Outline color on hover */
}

Examples

Basic Radio Button Group

<template>
  <div class="tw-space-y-2">
    <div v-for="option in options" :key="option.value">
      <VcRadioButton
        v-model="selectedOption"
        :value="option.value"
        :label="option.label"
      />
    </div>

    <div class="tw-mt-4 tw-text-sm tw-text-[color:var(--neutrals-500)]">
      Selected: {{ selectedOption }}
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton } from '@vc-shell/framework';

const options = [
  { value: 'email', label: 'Email' },
  { value: 'phone', label: 'Phone' },
  { value: 'mail', label: 'Mail' },
];

const selectedOption = ref('email');
</script>

Radio Buttons with Object Values

<template>
  <div class="tw-space-y-2">
    <div v-for="product in products" :key="product.id">
      <VcRadioButton
        v-model="selectedProduct"
        :value="product"
        :label="product.name"
      />
    </div>

    <div class="tw-mt-4 tw-p-3 tw-bg-[color:var(--neutrals-100)] tw-rounded">
      <p class="tw-font-medium">Selected Product:</p>
      <p>Name: {{ selectedProduct.name }}</p>
      <p>Price: ${{ selectedProduct.price }}</p>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton } from '@vc-shell/framework';

const products = [
  { id: 1, name: 'Basic Plan', price: 9.99 },
  { id: 2, name: 'Standard Plan', price: 19.99 },
  { id: 3, name: 'Premium Plan', price: 29.99 },
];

const selectedProduct = ref(products[0]);
</script>

Radio Button with Binary Mode

<template>
  <div>
    <VcRadioButton
      v-model="acceptTerms"
      :binary="true"
      label="I accept the terms and conditions"
    />

    <div class="tw-mt-4">
      <VcButton :disabled="!acceptTerms" @click="proceedToNextStep">
        Continue
      </VcButton>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton, VcButton } from '@vc-shell/framework';

const acceptTerms = ref(false);

function proceedToNextStep() {
  // Handle form submission or navigation
  console.log('Proceeding to next step');
}
</script>

Disabled Radio Button

<template>
  <div class="tw-space-y-2">
    <VcRadioButton
      v-model="selectedOption"
      value="option1"
      label="Available Option"
    />

    <VcRadioButton
      v-model="selectedOption"
      value="option2"
      label="Unavailable Option"
      disabled
    />
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton } from '@vc-shell/framework';

const selectedOption = ref('option1');
</script>

Radio Buttons with Error State

<template>
  <form @submit.prevent="validateForm">
    <div class="tw-mb-4">
      <p class="tw-font-medium tw-mb-2">Select your preferred contact method:</p>
      <div class="tw-space-y-2">
        <VcRadioButton
          v-for="option in contactOptions"
          :key="option.value"
          v-model="selectedContactMethod"
          :value="option.value"
          :label="option.label"
          :error="formSubmitted && !selectedContactMethod"
          :errorMessage="formSubmitted && !selectedContactMethod ? 'Please select a contact method' : ''"
        />
      </div>
    </div>

    <VcButton type="submit">
      Submit
    </VcButton>
  </form>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcRadioButton, VcButton } from '@vc-shell/framework';

const contactOptions = [
  { value: 'email', label: 'Email' },
  { value: 'phone', label: 'Phone' },
  { value: 'mail', label: 'Mail' },
];

const selectedContactMethod = ref('');
const formSubmitted = ref(false);

function validateForm() {
  formSubmitted.value = true;

  if (selectedContactMethod.value) {
    // Form is valid, proceed
    console.log('Form submitted with:', selectedContactMethod.value);
  }
}
</script>

Grouped Radio Buttons with Custom Error Slot

<template>
  <form @submit.prevent="submitForm">
    <fieldset class="tw-border tw-border-[color:var(--neutrals-300)] tw-p-4 tw-rounded">
      <legend class="tw-px-2 tw-font-medium">Delivery Method</legend>

      <div class="tw-space-y-2">
        <VcRadioButton
          v-for="option in deliveryOptions"
          :key="option.value"
          v-model="selectedDelivery"
          :value="option.value"
          :label="option.label"
          :error="errors.delivery"
        >
          <template #error v-if="errors.delivery && option.value === deliveryOptions[0].value">
            <div class="tw-flex tw-items-center tw-mt-1">
              <VcIcon 
                icon="material-error_outline" 
                class="tw-mr-1 tw-text-[color:var(--danger-500)]" 
                size="s" 
              />
              <span class="tw-text-[color:var(--danger-500)] tw-text-sm">
                Please select a delivery method
              </span>
            </div>
          </template>
        </VcRadioButton>
      </div>
    </fieldset>

    <VcButton type="submit" class="tw-mt-4">
      Submit Order
    </VcButton>
  </form>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { VcRadioButton, VcButton, VcIcon } from '@vc-shell/framework';

const deliveryOptions = [
  { value: 'standard', label: 'Standard Delivery (3-5 business days)' },
  { value: 'express', label: 'Express Delivery (1-2 business days)' },
  { value: 'same-day', label: 'Same Day Delivery (where available)' },
];

const selectedDelivery = ref('');
const errors = reactive({
  delivery: false
});

function submitForm() {
  errors.delivery = !selectedDelivery.value;

  if (!errors.delivery) {
    // Form is valid, proceed with submission
    console.log('Order submitted with delivery method:', selectedDelivery.value);
  }
}
</script>

Integration with Other Form Components

<template>
  <form @submit.prevent="submitForm" class="tw-space-y-4">
    <div>
      <VcLabel>Payment Method</VcLabel>
      <div class="tw-space-y-2 tw-mt-2">
        <VcRadioButton
          v-for="method in paymentMethods"
          :key="method.value"
          v-model="formData.paymentMethod"
          :value="method.value"
          :label="method.label"
        />
      </div>
    </div>

    <div v-if="formData.paymentMethod === 'card'">
      <VcLabel>Card Details</VcLabel>
      <VcInput 
        v-model="formData.cardNumber" 
        placeholder="Card Number" 
        class="tw-mb-2" 
      />
      <div class="tw-flex tw-gap-2">
        <VcInput v-model="formData.expiryDate" placeholder="MM/YY" />
        <VcInput v-model="formData.cvv" placeholder="CVV" />
      </div>
    </div>

    <div v-if="formData.paymentMethod === 'paypal'">
      <VcLabel>PayPal Email</VcLabel>
      <VcInput v-model="formData.paypalEmail" placeholder="Email" />
    </div>

    <VcButton type="submit">
      Proceed to Payment
    </VcButton>
  </form>
</template>

<script lang="ts" setup>
import { reactive } from 'vue';
import { VcRadioButton, VcButton, VcLabel, VcInput } from '@vc-shell/framework';

const paymentMethods = [
  { value: 'card', label: 'Credit/Debit Card' },
  { value: 'paypal', label: 'PayPal' },
  { value: 'bank', label: 'Bank Transfer' },
];

const formData = reactive({
  paymentMethod: 'card',
  cardNumber: '',
  expiryDate: '',
  cvv: '',
  paypalEmail: '',
});

function submitForm() {
  console.log('Form submitted with data:', formData);
}
</script>