Skip to content

Radio

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

Radio button allows a user to select a single option from a set of mutually exclusive choices. It's ideal for forms where only one selection is permitted.

Examples

Vue
Lit
React
Live Preview

Default Radio

Basic radio buttons with default theme (primary)

Radio Group

Multiple radio button groups

Size Variants

Three size options: small, medium (default), and large

Theme Variants

Choose from default (green), primary (blue), or monochrome (black/white) themes

Disabled State

Radio buttons can be disabled to prevent interaction

Keyboard Navigation with Disabled Radios

Disabled radios are automatically skipped when navigating with arrow keys. Try using / or / arrow keys, or HOME and END (to jump to start and end respectively) to navigate between options. The disabled middle option will be skipped.

Label Position

Labels can be positioned at the end (default) or start of the radio button

Inline Layout

Radio buttons can be laid out horizontally for compact forms

External Label Support

Radio buttons support optional external labels, helper text, and error messages for validation feedback.

CSS Shadow Parts Customization

Use CSS Shadow Parts to customize the component's appearance.

View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Default Radio</h2>
      <p class="mbs2 mbe3">Basic radio buttons with default theme (primary)</p>
    </div>
    <VueFieldset legend="Choose your plan">
      <div style="display: flex; flex-direction: column; gap: 0.75rem;">
        <VueRadio
          name="plan"
          value="free"
          label-text="Free Plan"
        />
        <VueRadio
          name="plan"
          value="pro"
          label-text="Pro Plan"
          :checked="true"
        />
        <VueRadio
          name="plan"
          value="enterprise"
          label-text="Enterprise Plan"
        />
      </div>
    </VueFieldset>

    <div class="mbe4">
      <h2>Radio Group</h2>
      <p class="mbs2 mbe3">Multiple radio button groups</p>
    </div>
    <div
      class="mbe4"
      style="display: flex; flex-direction: column; gap: 1.5rem;"
    >
      <VueFieldset legend="Select Framework">
        <div style="display: flex; flex-direction: column; gap: 0.75rem;">
          <VueRadio
            name="framework"
            value="react"
            label-text="React"
          />
          <VueRadio
            name="framework"
            value="vue"
            label-text="Vue"
            :checked="true"
          />
          <VueRadio
            name="framework"
            value="angular"
            label-text="Angular"
          />
          <VueRadio
            name="framework"
            value="svelte"
            label-text="Svelte"
          />
        </div>
      </VueFieldset>

      <VueFieldset legend="Deployment Environment">
        <div style="display: flex; flex-direction: column; gap: 0.75rem;">
          <VueRadio
            name="environment"
            value="development"
            label-text="Development"
            :checked="true"
          />
          <VueRadio
            name="environment"
            value="staging"
            label-text="Staging"
          />
          <VueRadio
            name="environment"
            value="production"
            label-text="Production"
          />
        </div>
      </VueFieldset>
    </div>

    <div class="mbe4">
      <h2>Size Variants</h2>
      <p class="mbs2 mbe3">Three size options: small, medium (default), and large</p>
    </div>
    <div
      class="mbe4"
      style="display: flex; gap: 2rem; align-items: center;"
    >
      <VueRadio
        name="size-demo"
        value="small"
        size="small"
        label-text="Small"
        :checked="true"
      />
      <VueRadio
        name="size-demo2"
        value="medium"
        size="medium"
        label-text="Medium"
        :checked="true"
      />
      <VueRadio
        name="size-demo3"
        value="large"
        size="large"
        label-text="Large"
        :checked="true"
      />
    </div>

    <div class="mbe4">
      <h2>Theme Variants</h2>
      <p class="mbs2 mbe3">Choose from default (green), primary (blue), or monochrome (black/white) themes</p>
    </div>
    <div
      class="mbe4"
      style="display: flex; flex-direction: column; gap: 1.5rem;"
    >
      <VueFieldset legend="Default Theme (Blue)">
        <div style="display: flex; gap: 1rem;">
          <VueRadio
            name="theme-default"
            value="1"
            theme="default"
            label-text="Unchecked"
          />
          <VueRadio
            name="theme-default"
            value="2"
            theme="default"
            label-text="Checked"
            :checked="true"
          />
        </div>
      </VueFieldset>
      <VueFieldset legend="Primary Theme (Blue)">
        <div style="display: flex; gap: 1rem;">
          <VueRadio
            name="theme-primary"
            value="1"
            theme="primary"
            label-text="Unchecked"
          />
          <VueRadio
            name="theme-primary"
            value="2"
            theme="primary"
            label-text="Checked"
            :checked="true"
          />
        </div>
      </VueFieldset>
      <VueFieldset legend="Success Theme (Green)">
        <div style="display: flex; gap: 1rem;">
          <VueRadio
            name="theme-success"
            value="1"
            theme="success"
            label-text="Unchecked"
          />
          <VueRadio
            name="theme-success"
            value="2"
            theme="success"
            label-text="Checked"
            :checked="true"
          />
        </div>
      </VueFieldset>

      <VueFieldset legend="Monochrome Theme (Black/White)">
        <div style="display: flex; gap: 1rem;">
          <VueRadio
            name="theme-mono"
            value="1"
            theme="monochrome"
            label-text="Unchecked"
          />
          <VueRadio
            name="theme-mono"
            value="2"
            theme="monochrome"
            label-text="Checked"
            :checked="true"
          />
        </div>
      </VueFieldset>
    </div>

    <div class="mbe4">
      <h2>Disabled State</h2>
      <p class="mbs2 mbe3">Radio buttons can be disabled to prevent interaction</p>
    </div>
    <div
      class="mbe4"
      style="display: flex; gap: 1rem;"
    >
      <VueRadio
        name="disabled-demo"
        value="1"
        label-text="Disabled Unchecked"
        :disabled="true"
      />
      <VueRadio
        name="disabled-demo2"
        value="2"
        label-text="Disabled Checked"
        :checked="true"
        :disabled="true"
      />
    </div>

    <div class="mbe4">
      <h2>Keyboard Navigation with Disabled Radios</h2>
      <p class="mbs2 mbe3">
        Disabled radios are automatically skipped when navigating with arrow keys.
        Try using <kbd>↑</kbd> / <kbd>↓</kbd> or <kbd>←</kbd> / <kbd>→</kbd> arrow keys, or <kbd>HOME</kbd> and <kbd>END</kbd> (to jump to start and end respectively) to navigate between options.
        The disabled middle option will be skipped.
      </p>
    </div>
    <VueFieldset legend="Select Shipping Method">
      <div style="display: flex; flex-direction: column; gap: 0.75rem;">
        <VueRadio
          name="keyboard-nav-demo"
          value="standard"
          label-text="Standard Shipping - $5.99"
          :checked="true"
        />
        <VueRadio
          name="keyboard-nav-demo"
          value="express"
          label-text="Express Shipping - Currently Unavailable"
          :disabled="true"
        />
        <VueRadio
          name="keyboard-nav-demo"
          value="overnight"
          label-text="Overnight Shipping - $24.99"
        />
      </div>
    </VueFieldset>

    <div class="mbe4">
      <h2>Label Position</h2>
      <p class="mbs2 mbe3">Labels can be positioned at the end (default) or start of the radio button</p>
    </div>
    <div
      class="mbe4"
      style="display: flex; flex-direction: column; gap: 1rem;"
    >
      <VueRadio
        name="position-demo"
        value="1"
        label-text="Label on End (Default)"
        label-position="end"
        :checked="true"
      />
      <VueRadio
        name="position-demo2"
        value="2"
        label-text="Label on Start"
        label-position="start"
        :checked="true"
      />
    </div>

    <div class="mbe4">
      <h2>Inline Layout</h2>
      <p class="mbs2 mbe3">Radio buttons can be laid out horizontally for compact forms</p>
    </div>
    <VueFieldset legend="Gender">
      <div style="display: flex; gap: 1.5rem;">
        <VueRadio
          name="gender"
          value="male"
          label-text="Male"
        />
        <VueRadio
          name="gender"
          value="female"
          label-text="Female"
          :checked="true"
        />
        <VueRadio
          name="gender"
          value="other"
          label-text="Other"
        />
      </div>
    </VueFieldset>

    <div class="mbe4">
      <h2>External Label Support</h2>
      <p class="mbs2 mbe3">
        Radio buttons support optional external labels, helper text, and error messages for validation feedback.
      </p>
    </div>
    <div class="stacked mbe4">
      <VueRadio
        name="shipping-method"
        value="standard"
        label="Shipping Method"
        label-text="Standard Shipping (5-7 days)"
        :checked="true"
        class="mbe2"
      />

      <VueRadio
        name="payment-method"
        value="credit"
        label="Payment Method"
        label-text="Credit Card"
        help-text="We accept all major credit cards"
        :checked="true"
        class="mbe2"
      />

      <VueRadio
        name="terms-agreement"
        value="agreed"
        label="Terms and Conditions"
        label-text="I agree to the terms and conditions"
        :required="true"
        help-text="Please read and accept our terms to proceed"
        class="mbe2"
      />

      <VueRadio
        name="age-verify"
        value="confirmed"
        label="Age Verification"
        label-text="I confirm I am 18 years or older"
        :required="true"
        :invalid="true"
        error-message="You must confirm you are 18 or older to proceed"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>CSS Shadow Parts Customization</h2>
      <p class="mbs2 mbe3">
        Use CSS Shadow Parts to customize the component's appearance.
      </p>
    </div>
    <div class="stacked-mobile mbe4">
      <VueRadio
        name="custom"
        value="1"
        label-text="Customized indicator"
        :checked="true"
        class="custom-radio-1"
      />
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { VueRadio } from "agnosticui-core/radio/vue";
import { VueFieldset } from "agnosticui-core/fieldset/vue";

export default defineComponent({
  name: "RadioExamples",
  components: {
    VueRadio,
    VueFieldset,
  },
});
</script>

<style>
/* CSS Shadow Parts customization examples */
.custom-radio-1::part(ag-radio-indicator) {
  border-width: 3px;
  transform: scale(1.3);
}
</style>

Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/radio';
import 'agnosticui-core/fieldset';

export class RadioLitExamples extends LitElement {
  // Render in light DOM to access global utility classes
  createRenderRoot() {
    return this;
  }

  render() {
    return html`
      <section>
        <div class="mbe4">
          <h2>Default Radio</h2>
          <p class="mbs2 mbe3">Basic radio buttons with default theme (primary)</p>
        </div>
        <ag-fieldset legend="Choose your plan">
          <div style="display: flex; flex-direction: column; gap: 0.75rem;">
            <ag-radio
              name="plan"
              value="free"
              .labelText=${"Free Plan"}
            ></ag-radio>
            <ag-radio
              name="plan"
              value="pro"
              .labelText=${"Pro Plan"}
              .checked=${true}
            ></ag-radio>
            <ag-radio
              name="plan"
              value="enterprise"
              .labelText=${"Enterprise Plan"}
            ></ag-radio>
          </div>
        </ag-fieldset>

        <div class="mbe4">
          <h2>Radio Group</h2>
          <p class="mbs2 mbe3">Multiple radio button groups</p>
        </div>
        <div
          class="mbe4"
          style="display: flex; flex-direction: column; gap: 1.5rem;"
        >
          <ag-fieldset legend="Select Framework">
            <div style="display: flex; flex-direction: column; gap: 0.75rem;">
              <ag-radio
                name="framework"
                value="react"
                .labelText=${"React"}
              ></ag-radio>
              <ag-radio
                name="framework"
                value="vue"
                .labelText=${"Vue"}
                .checked=${true}
              ></ag-radio>
              <ag-radio
                name="framework"
                value="angular"
                .labelText=${"Angular"}
              ></ag-radio>
              <ag-radio
                name="framework"
                value="svelte"
                .labelText=${"Svelte"}
              ></ag-radio>
            </div>
          </ag-fieldset>

          <ag-fieldset legend="Deployment Environment">
            <div style="display: flex; flex-direction: column; gap: 0.75rem;">
              <ag-radio
                name="environment"
                value="development"
                .labelText=${"Development"}
                .checked=${true}
              ></ag-radio>
              <ag-radio
                name="environment"
                value="staging"
                .labelText=${"Staging"}
              ></ag-radio>
              <ag-radio
                name="environment"
                value="production"
                .labelText=${"Production"}
              ></ag-radio>
            </div>
          </ag-fieldset>
        </div>

        <div class="mbe4">
          <h2>Size Variants</h2>
          <p class="mbs2 mbe3">Three size options: small, medium (default), and large</p>
        </div>
        <div
          class="mbe4"
          style="display: flex; gap: 2rem; align-items: center;"
        >
          <ag-radio
            name="size-demo"
            value="small"
            size="small"
            .labelText=${"Small"}
            .checked=${true}
          ></ag-radio>
          <ag-radio
            name="size-demo2"
            value="medium"
            size="medium"
            .labelText=${"Medium"}
            .checked=${true}
          ></ag-radio>
          <ag-radio
            name="size-demo3"
            value="large"
            size="large"
            .labelText=${"Large"}
            .checked=${true}
          ></ag-radio>
        </div>

        <div class="mbe4">
          <h2>Theme Variants</h2>
          <p class="mbs2 mbe3">Choose from default (green), primary (blue), or monochrome (black/white) themes</p>
        </div>
        <div
          class="mbe4"
          style="display: flex; flex-direction: column; gap: 1.5rem;"
        >
          <ag-fieldset legend="Default Theme (Blue)">
            <div style="display: flex; gap: 1rem;">
              <ag-radio
                name="theme-default"
                value="1"
                theme="default"
                .labelText=${"Unchecked"}
              ></ag-radio>
              <ag-radio
                name="theme-default"
                value="2"
                theme="default"
                .labelText=${"Checked"}
                .checked=${true}
              ></ag-radio>
            </div>
          </ag-fieldset>
          <ag-fieldset legend="Primary Theme (Blue)">
            <div style="display: flex; gap: 1rem;">
              <ag-radio
                name="theme-primary"
                value="1"
                theme="primary"
                .labelText=${"Unchecked"}
              ></ag-radio>
              <ag-radio
                name="theme-primary"
                value="2"
                theme="primary"
                .labelText=${"Checked"}
                .checked=${true}
              ></ag-radio>
            </div>
          </ag-fieldset>
          <ag-fieldset legend="Success Theme (Green)">
            <div style="display: flex; gap: 1rem;">
              <ag-radio
                name="theme-success"
                value="1"
                theme="success"
                .labelText=${"Unchecked"}
              ></ag-radio>
              <ag-radio
                name="theme-success"
                value="2"
                theme="success"
                .labelText=${"Checked"}
                .checked=${true}
              ></ag-radio>
            </div>
          </ag-fieldset>

          <ag-fieldset legend="Monochrome Theme (Black/White)">
            <div style="display: flex; gap: 1rem;">
              <ag-radio
                name="theme-mono"
                value="1"
                theme="monochrome"
                .labelText=${"Unchecked"}
              ></ag-radio>
              <ag-radio
                name="theme-mono"
                value="2"
                theme="monochrome"
                .labelText=${"Checked"}
                .checked=${true}
              ></ag-radio>
            </div>
          </ag-fieldset>
        </div>

        <div class="mbe4">
          <h2>Disabled State</h2>
          <p class="mbs2 mbe3">Radio buttons can be disabled to prevent interaction</p>
        </div>
        <div
          class="mbe4"
          style="display: flex; gap: 1rem;"
        >
          <ag-radio
            name="disabled-demo"
            value="1"
            .labelText=${"Disabled Unchecked"}
            .disabled=${true}
          ></ag-radio>
          <ag-radio
            name="disabled-demo2"
            value="2"
            .labelText=${"Disabled Checked"}
            .checked=${true}
            .disabled=${true}
          ></ag-radio>
        </div>

        <div class="mbe4">
          <h2>Keyboard Navigation with Disabled Radios</h2>
          <p class="mbs2 mbe3">
            Disabled radios are automatically skipped when navigating with arrow keys.
            Try using <kbd>↑</kbd> / <kbd>↓</kbd> or <kbd>←</kbd> / <kbd>→</kbd> arrow keys, or <kbd>HOME</kbd> and <kbd>END</kbd> (to jump to start and end respectively) to navigate between options.
            The disabled middle option will be skipped.
          </p>
        </div>
        <ag-fieldset legend="Select Shipping Method">
          <div style="display: flex; flex-direction: column; gap: 0.75rem;">
            <ag-radio
              name="keyboard-nav-demo"
              value="standard"
              .labelText=${"Standard Shipping - $5.99"}
              .checked=${true}
            ></ag-radio>
            <ag-radio
              name="keyboard-nav-demo"
              value="express"
              .labelText=${"Express Shipping - Currently Unavailable"}
              .disabled=${true}
            ></ag-radio>
            <ag-radio
              name="keyboard-nav-demo"
              value="overnight"
              .labelText=${"Overnight Shipping - $24.99"}
            ></ag-radio>
          </div>
        </ag-fieldset>

        <div class="mbe4">
          <h2>Label Position</h2>
          <p class="mbs2 mbe3">Labels can be positioned at the end (default) or start of the radio button</p>
        </div>
        <div
          class="mbe4"
          style="display: flex; flex-direction: column; gap: 1rem;"
        >
          <ag-radio
            name="position-demo"
            value="1"
            .labelText=${"Label on End (Default)"}
            label-position="end"
            .checked=${true}
          ></ag-radio>
          <ag-radio
            name="position-demo2"
            value="2"
            .labelText=${"Label on Start"}
            label-position="start"
            .checked=${true}
          ></ag-radio>
        </div>

        <div class="mbe4">
          <h2>Inline Layout</h2>
          <p class="mbs2 mbe3">Radio buttons can be laid out horizontally for compact forms</p>
        </div>
        <ag-fieldset legend="Gender">
          <div style="display: flex; gap: 1.5rem;">
            <ag-radio
              name="gender"
              value="male"
              .labelText=${"Male"}
            ></ag-radio>
            <ag-radio
              name="gender"
              value="female"
              .labelText=${"Female"}
              .checked=${true}
            ></ag-radio>
            <ag-radio
              name="gender"
              value="other"
              .labelText=${"Other"}
            ></ag-radio>
          </div>
        </ag-fieldset>

        <div class="mbe4">
          <h2>External Label Support</h2>
          <p class="mbs2 mbe3">
            Radio buttons support optional external labels, helper text, and error messages for validation feedback.
          </p>
        </div>
        <div class="stacked mbe4">
          <ag-radio
            name="shipping-method"
            value="standard"
            label="Shipping Method"
            .labelText=${"Standard Shipping (5-7 days)"}
            .checked=${true}
            class="mbe2"
          ></ag-radio>

          <ag-radio
            name="payment-method"
            value="credit"
            label="Payment Method"
            .labelText=${"Credit Card"}
            help-text="We accept all major credit cards"
            .checked=${true}
            class="mbe2"
          ></ag-radio>

          <ag-radio
            name="terms-agreement"
            value="agreed"
            label="Terms and Conditions"
            .labelText=${"I agree to the terms and conditions"}
            .required=${true}
            help-text="Please read and accept our terms to proceed"
            class="mbe2"
          ></ag-radio>

          <ag-radio
            name="age-verify"
            value="confirmed"
            label="Age Verification"
            .labelText=${"I confirm I am 18 years or older"}
            .required=${true}
            .invalid=${true}
            error-message="You must confirm you are 18 or older to proceed"
            class="mbe2"
          ></ag-radio>
        </div>

        <div class="mbe4">
          <h2>CSS Shadow Parts Customization</h2>
          <p class="mbs2 mbe3">
            Use CSS Shadow Parts to customize the component's appearance.
          </p>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-radio
            name="custom"
            value="1"
            .labelText=${"Customized indicator"}
            .checked=${true}
            class="custom-radio-1"
          ></ag-radio>
        </div>
      </section>

      <style>
        /* CSS Shadow Parts customization examples */
        .custom-radio-1::part(ag-radio-indicator) {
          border-width: 3px;
          transform: scale(1.3);
        }
      </style>
    `;
  }
}

// Register the custom element
customElements.define('radio-lit-examples', RadioLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { ReactRadio } from "agnosticui-core/radio/react";
import { ReactFieldset } from "agnosticui-core/fieldset/react";

export default function RadioReactExamples() {
  return (
    <section>
      <div className="mbe4">
        <h2>Default Radio</h2>
        <p className="mbs2 mbe3">Basic radio buttons with default theme (primary)</p>
      </div>
      <ReactFieldset legend="Choose your plan">
        <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
          <ReactRadio name="plan" value="free" labelText="Free Plan" />
          <ReactRadio name="plan" value="pro" labelText="Pro Plan" checked />
          <ReactRadio name="plan" value="enterprise" labelText="Enterprise Plan" />
        </div>
      </ReactFieldset>

      <div className="mbe4">
        <h2>Radio Group</h2>
        <p className="mbs2 mbe3">Multiple radio button groups</p>
      </div>
      <div
        className="mbe4"
        style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}
      >
        <ReactFieldset legend="Select Framework">
          <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
            <ReactRadio name="framework" value="react" labelText="React" />
            <ReactRadio name="framework" value="vue" labelText="Vue" checked />
            <ReactRadio name="framework" value="angular" labelText="Angular" />
            <ReactRadio name="framework" value="svelte" labelText="Svelte" />
          </div>
        </ReactFieldset>

        <ReactFieldset legend="Deployment Environment">
          <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
            <ReactRadio
              name="environment"
              value="development"
              labelText="Development"
              checked
            />
            <ReactRadio name="environment" value="staging" labelText="Staging" />
            <ReactRadio name="environment" value="production" labelText="Production" />
          </div>
        </ReactFieldset>
      </div>

      <div className="mbe4">
        <h2>Size Variants</h2>
        <p className="mbs2 mbe3">Three size options: small, medium (default), and large</p>
      </div>
      <div className="mbe4" style={{ display: "flex", gap: "2rem", alignItems: "center" }}>
        <ReactRadio name="size-demo" value="small" size="small" labelText="Small" checked />
        <ReactRadio
          name="size-demo2"
          value="medium"
          size="medium"
          labelText="Medium"
          checked
        />
        <ReactRadio name="size-demo3" value="large" size="large" labelText="Large" checked />
      </div>

      <div className="mbe4">
        <h2>Theme Variants</h2>
        <p className="mbs2 mbe3">
          Choose from default (green), primary (blue), or monochrome (black/white) themes
        </p>
      </div>
      <div
        className="mbe4"
        style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}
      >
        <ReactFieldset legend="Default Theme (Blue)">
          <div style={{ display: "flex", gap: "1rem" }}>
            <ReactRadio name="theme-default" value="1" theme="default" labelText="Unchecked" />
            <ReactRadio
              name="theme-default"
              value="2"
              theme="default"
              labelText="Checked"
              checked
            />
          </div>
        </ReactFieldset>
        <ReactFieldset legend="Primary Theme (Blue)">
          <div style={{ display: "flex", gap: "1rem" }}>
            <ReactRadio name="theme-primary" value="1" theme="primary" labelText="Unchecked" />
            <ReactRadio
              name="theme-primary"
              value="2"
              theme="primary"
              labelText="Checked"
              checked
            />
          </div>
        </ReactFieldset>
        <ReactFieldset legend="Success Theme (Green)">
          <div style={{ display: "flex", gap: "1rem" }}>
            <ReactRadio name="theme-success" value="1" theme="success" labelText="Unchecked" />
            <ReactRadio
              name="theme-success"
              value="2"
              theme="success"
              labelText="Checked"
              checked
            />
          </div>
        </ReactFieldset>

        <ReactFieldset legend="Monochrome Theme (Black/White)">
          <div style={{ display: "flex", gap: "1rem" }}>
            <ReactRadio
              name="theme-mono"
              value="1"
              theme="monochrome"
              labelText="Unchecked"
            />
            <ReactRadio
              name="theme-mono"
              value="2"
              theme="monochrome"
              labelText="Checked"
              checked
            />
          </div>
        </ReactFieldset>
      </div>

      <div className="mbe4">
        <h2>Disabled State</h2>
        <p className="mbs2 mbe3">Radio buttons can be disabled to prevent interaction</p>
      </div>
      <div className="mbe4" style={{ display: "flex", gap: "1rem" }}>
        <ReactRadio
          name="disabled-demo"
          value="1"
          labelText="Disabled Unchecked"
          disabled
        />
        <ReactRadio
          name="disabled-demo2"
          value="2"
          labelText="Disabled Checked"
          checked
          disabled
        />
      </div>

      <div className="mbe4">
        <h2>Keyboard Navigation with Disabled Radios</h2>
        <p className="mbs2 mbe3">
          Disabled radios are automatically skipped when navigating with arrow keys. Try using{" "}
          <kbd>↑</kbd> / <kbd>↓</kbd> or <kbd>←</kbd> / <kbd>→</kbd> arrow keys, or{" "}
          <kbd>HOME</kbd> and <kbd>END</kbd> (to jump to start and end respectively) to navigate
          between options. The disabled middle option will be skipped.
        </p>
      </div>
      <ReactFieldset legend="Select Shipping Method">
        <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
          <ReactRadio
            name="keyboard-nav-demo"
            value="standard"
            labelText="Standard Shipping - $5.99"
            checked
          />
          <ReactRadio
            name="keyboard-nav-demo"
            value="express"
            labelText="Express Shipping - Currently Unavailable"
            disabled
          />
          <ReactRadio
            name="keyboard-nav-demo"
            value="overnight"
            labelText="Overnight Shipping - $24.99"
          />
        </div>
      </ReactFieldset>

      <div className="mbe4">
        <h2>Label Position</h2>
        <p className="mbs2 mbe3">
          Labels can be positioned at the end (default) or start of the radio button
        </p>
      </div>
      <div
        className="mbe4"
        style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
      >
        <ReactRadio
          name="position-demo"
          value="1"
          labelText="Label on End (Default)"
          labelPosition="end"
          checked
        />
        <ReactRadio
          name="position-demo2"
          value="2"
          labelText="Label on Start"
          labelPosition="start"
          checked
        />
      </div>

      <div className="mbe4">
        <h2>Inline Layout</h2>
        <p className="mbs2 mbe3">Radio buttons can be laid out horizontally for compact forms</p>
      </div>
      <ReactFieldset legend="Gender">
        <div style={{ display: "flex", gap: "1.5rem" }}>
          <ReactRadio name="gender" value="male" labelText="Male" />
          <ReactRadio name="gender" value="female" labelText="Female" checked />
          <ReactRadio name="gender" value="other" labelText="Other" />
        </div>
      </ReactFieldset>

      <div className="mbe4">
        <h2>External Label Support</h2>
        <p className="mbs2 mbe3">
          Radio buttons support optional external labels, helper text, and error messages for
          validation feedback.
        </p>
      </div>
      <div className="stacked mbe4">
        <ReactRadio
          name="shipping-method"
          value="standard"
          label="Shipping Method"
          labelText="Standard Shipping (5-7 days)"
          checked
          className="mbe2"
        />

        <ReactRadio
          name="payment-method"
          value="credit"
          label="Payment Method"
          labelText="Credit Card"
          helpText="We accept all major credit cards"
          checked
          className="mbe2"
        />

        <ReactRadio
          name="terms-agreement"
          value="agreed"
          label="Terms and Conditions"
          labelText="I agree to the terms and conditions"
          required
          helpText="Please read and accept our terms to proceed"
          className="mbe2"
        />

        <ReactRadio
          name="age-verify"
          value="confirmed"
          label="Age Verification"
          labelText="I confirm I am 18 years or older"
          required
          invalid
          errorMessage="You must confirm you are 18 or older to proceed"
          className="mbe2"
        />
      </div>

      <div className="mbe4">
        <h2>CSS Shadow Parts Customization</h2>
        <p className="mbs2 mbe3">
          Use CSS Shadow Parts to customize the component's appearance.
        </p>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactRadio
          name="custom"
          value="1"
          labelText="Customized indicator"
          checked
          className="custom-radio-1"
        />
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Radio

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <section>
    <VueFieldset legend="Choose your plan">
      <VueRadio
        name="plan"
        value="free"
        label-text="Free Plan"
      />
      <VueRadio
        name="plan"
        value="pro"
        label-text="Pro Plan"
        :checked="true"
      />
      <VueRadio
        name="plan"
        value="enterprise"
        label-text="Enterprise Plan"
      />
    </VueFieldset>

    <VueFieldset legend="Select framework">
      <VueRadio
        name="framework"
        value="react"
        theme="primary"
        label-text="React"
      />
      <VueRadio
        name="framework"
        value="vue"
        theme="primary"
        label-text="Vue"
        :checked="true"
      />
      <VueRadio
        name="framework"
        value="angular"
        theme="primary"
        label-text="Angular"
      />
    </VueFieldset>

    <VueFieldset legend="Gender">
      <div style="display: flex; gap: 1.5rem;">
        <VueRadio
          name="gender"
          value="male"
          label-text="Male"
        />
        <VueRadio
          name="gender"
          value="female"
          label-text="Female"
          :checked="true"
        />
        <VueRadio
          name="gender"
          value="other"
          label-text="Other"
        />
      </div>
    </VueFieldset>
  </section>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { VueRadio } from 'agnosticui-core/radio/vue';
import { VueFieldset } from 'agnosticui-core/fieldset/vue';

export default defineComponent({
  components: { VueRadio, VueFieldset }
});
</script>
React
tsx
import { ReactRadio } from 'agnosticui-core/radio/react';
import { ReactFieldset } from 'agnosticui-core/fieldset/react';

export default function Example() {
  return (
    <section>
      <ReactFieldset legend="Choose your plan">
        <ReactRadio
          name="plan"
          value="free"
          labelText="Free Plan"
        />
        <ReactRadio
          name="plan"
          value="pro"
          labelText="Pro Plan"
          checked
        />
        <ReactRadio
          name="plan"
          value="enterprise"
          labelText="Enterprise Plan"
        />
      </ReactFieldset>

      <ReactFieldset legend="Select framework">
        <ReactRadio
          name="framework"
          value="react"
          theme="primary"
          labelText="React"
        />
        <ReactRadio
          name="framework"
          value="vue"
          theme="primary"
          labelText="Vue"
          checked
        />
        <ReactRadio
          name="framework"
          value="angular"
          theme="primary"
          labelText="Angular"
        />
      </ReactFieldset>

      <ReactFieldset legend="Gender">
        <div style={{ display: 'flex', gap: '1.5rem' }}>
          <ReactRadio
            name="gender"
            value="male"
            labelText="Male"
          />
          <ReactRadio
            name="gender"
            value="female"
            labelText="Female"
            checked
          />
          <ReactRadio
            name="gender"
            value="other"
            labelText="Other"
          />
        </div>
      </ReactFieldset>
    </section>
  );
}
Lit (Web Components)
html
<script type="module">
  import 'agnosticui-core/radio';
  import 'agnosticui-core/fieldset';
</script>

<section>
  <ag-fieldset legend="Choose your plan">
    <ag-radio
      name="plan"
      value="free"
      label-text="Free Plan"
    ></ag-radio>
    <ag-radio
      name="plan"
      value="pro"
      label-text="Pro Plan"
      checked
    ></ag-radio>
    <ag-radio
      name="plan"
      value="enterprise"
      label-text="Enterprise Plan"
    ></ag-radio>
  </ag-fieldset>

  <ag-fieldset legend="Select framework">
    <ag-radio
      name="framework"
      value="react"
      theme="primary"
      label-text="React"
    ></ag-radio>
    <ag-radio
      name="framework"
      value="vue"
      theme="primary"
      label-text="Vue"
      checked
    ></ag-radio>
    <ag-radio
      name="framework"
      value="angular"
      theme="primary"
      label-text="Angular"
    ></ag-radio>
  </ag-fieldset>

  <ag-fieldset legend="Gender">
    <div style="display: flex; gap: 1.5rem;">
      <ag-radio
        name="gender"
        value="male"
        label-text="Male"
      ></ag-radio>
      <ag-radio
        name="gender"
        value="female"
        label-text="Female"
        checked
      ></ag-radio>
      <ag-radio
        name="gender"
        value="other"
        label-text="Other"
      ></ag-radio>
    </div>
  </ag-fieldset>
</section>

Label Position

The labelPosition prop controls where the internal label text appears relative to the radio button. This affects the labelText (the label that wraps the radio), not the external label prop.

vue
<VueRadio
  name="option"
  value="1"
  label-text="Label on end (default)"
  label-position="end"
/>
<VueRadio
  name="option"
  value="2"
  label-text="Label on start"
  label-position="start"
/>

External Label Support

Radio buttons now support optional external labels, helper text, and error messages using the shared form control utilities. This is useful for:

  • Radio groups that need a descriptive label above them
  • Standalone radios that require validation feedback
  • Forms where consistent label/helper/error patterns are needed

Basic External Label

vue
<VueRadio
  name="shipping"
  value="standard"
  label="Shipping Method"
  label-text="Standard Shipping (5-7 days)"
  :checked="true"
/>

With Helper Text

vue
<VueRadio
  name="payment"
  value="credit"
  label="Payment Method"
  label-text="Credit Card"
  help-text="We accept all major credit cards"
/>

With Validation

vue
<VueRadio
  name="terms"
  value="agreed"
  label="Terms and Conditions"
  label-text="I agree to the terms and conditions"
  :required="true"
  :invalid="!termsAccepted"
  error-message="You must accept the terms to proceed"
/>

Radio Groups with External Label

vue
<template>
  <div>
    <VueRadio
      name="notifications"
      value="email"
      label="Notification Preferences"
      label-text="Email notifications"
      help-text="Choose how you'd like to be notified"
      :checked="true"
    />
    <VueRadio
      name="notifications"
      value="sms"
      label-text="SMS notifications"
    />
    <VueRadio
      name="notifications"
      value="push"
      label-text="Push notifications"
    />
  </div>
</template>

Note: The label prop creates an external label above the radio, while labelText is the internal label that wraps the radio itself. Most radios use only labelText, but external labels are useful for groups or when you need validation feedback.

Props

PropTypeDefaultDescription
namestring''Name attribute for the radio input (required for grouping - all radios with the same name are mutually exclusive)
valuestring''Value of the radio input
checkedbooleanfalseWhether the radio is checked
disabledbooleanfalseWhether the radio is disabled
size'small' | 'medium' | 'large''medium'Size of the radio button
theme'default' | 'primary' | 'success' | 'monochrome''primary'Color theme variant (default=blue alias to primary, primary=blue, success=green, monochrome=black/white)
labelTextstring''Label text for the radio button (internal label that wraps the radio)
labelPosition'end' | 'start''end'Position of labelText relative to radio button (end = after radio, start = before radio)
labelstring''Optional external label displayed above the radio (useful for groups or standalone with context)
labelHiddenbooleanfalseVisually hides the external label while keeping it accessible to screen readers
noLabelbooleanfalseCompletely removes the external label element
requiredbooleanfalseMarks the radio as required. Displays an asterisk (*) after the external label
invalidbooleanfalseMarks the radio as invalid. Sets aria-invalid and can display error message
errorMessagestring''Error message displayed when invalid. Linked via aria-describedby
helpTextstring''Helper text displayed below the radio. Linked via aria-describedby

Events

EventFrameworkDetailDescription
clickVue: @click
React: onClick
Lit: @click
MouseEventFired when the radio is clicked.
changeVue: @change
React: onChange
Lit: @change
{ checked: boolean, value: string, name: string }Fired when radio selection changes. Contains the checked state and form data.

Note: The Radio component supports dual-dispatch event propagation: it dispatches both DOM CustomEvents (usable with addEventListener) and invokes callback props (.onChange), giving you flexibility in how you handle events.

Event Usage Examples

Vue
vue
<template>
  <section>
    <VueRadio
      name="color"
      value="red"
      label-text="Red"
      @change="handleChange"
    />

    <VueRadio
      name="color"
      value="blue"
      label-text="Blue"
      v-model:checked="selectedColor"
    />

    <VueRadio
      name="color"
      value="green"
      label-text="Green"
      v-model:checked="selectedColor"
      @change="handleColorChange"
    />

    <div style="display: flex; flex-direction: column; gap: 0.5rem;">
      <VueRadio
        v-for="option in options"
        :key="option.value"
        name="options"
        :value="option.value"
        :label-text="option.label"
        :checked="selectedOption === option.value"
        @change="(detail) => selectedOption = detail.value"
      />
    </div>
  </section>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { VueRadio } from 'agnosticui-core/radio/vue';

const selectedColor = ref('blue');
const selectedOption = ref('option1');

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

const handleChange = (detail) => {
  console.log("Radio changed:", detail);
};

const handleColorChange = (detail) => {
  console.log("Color selected:", detail.value);
};
</script>
React
tsx
import { useState } from "react";
import { ReactRadio } from "agnosticui-core/radio/react";

export default function Example() {
  const [selectedColor, setSelectedColor] = useState("red");

  return (
    <section>
      <ReactRadio
        name="color"
        value="red"
        labelText="Red"
        checked={selectedColor === "red"}
        onChange={(e) => {
          console.log("Radio changed:", e.detail);
          setSelectedColor(e.detail.value);
        }}
      />
      <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
        <ReactRadio
          name="color"
          value="red"
          labelText="Red"
          checked={selectedColor === "red"}
          onChange={(e) => setSelectedColor(e.detail.value)}
        />
        <ReactRadio
          name="color"
          value="blue"
          labelText="Blue"
          checked={selectedColor === "blue"}
          onChange={(e) => setSelectedColor(e.detail.value)}
        />
        <ReactRadio
          name="color"
          value="green"
          labelText="Green"
          checked={selectedColor === "green"}
          onChange={(e) => setSelectedColor(e.detail.value)}
        />
      </div>
      <ReactRadio
        name="notification"
        value="enabled"
        labelText="Enable notifications"
        onClick={(e) => console.log("Clicked:", e)}
        onChange={(e) => console.log("Changed:", e.detail)}
      />
    </section>
  );
}
Lit (Web Components)
html
<script type="module">
  import "agnosticui-core/radio";

  const radio1 = document.querySelector("#radio1");
  radio1.addEventListener("change", (e) => {
    console.log("Event listener:", e.detail);
  });

  const radio2 = document.querySelector("#radio2");
  radio2.onChange = (e) => {
    console.log("Callback prop:", e.detail);
  };

  const radio3 = document.querySelector("#radio3");
  radio3.addEventListener("change", (e) => {
    console.log("DOM event:", e.detail);
  });
  radio3.onChange = (e) => {
    console.log("Callback also fired:", e.detail);
  };

  const radio4 = document.querySelector("#radio4");
  radio4.addEventListener("click", (e) => {
    console.log("Click event:", e);
  });
  radio4.onClick = (e) => {
    console.log("Click callback:", e);
  };

  const radios = document.querySelectorAll("ag-radio[name=\"group\"]");
  radios.forEach(radio => {
    radio.addEventListener("change", (e) => {
      console.log("Selected:", e.detail.value);
    });
  });
</script>

<section>
  <ag-radio
    id="radio1"
    name="example1"
    value="1"
    label-text="addEventListener pattern"
  ></ag-radio>

  <ag-radio
    id="radio2"
    name="example2"
    value="2"
    label-text="Callback prop pattern"
  ></ag-radio>

  <ag-radio
    id="radio3"
    name="example3"
    value="3"
    label-text="Dual-dispatch (both patterns)"
  ></ag-radio>

  <ag-radio
    id="radio4"
    name="example4"
    value="4"
    label-text="With click handlers"
  ></ag-radio>
  <div style="display: flex; flex-direction: column; gap: 0.5rem;">
    <ag-radio name="group" value="a" label-text="Option A"></ag-radio>
    <ag-radio name="group" value="b" label-text="Option B"></ag-radio>
    <ag-radio name="group" value="c" label-text="Option C"></ag-radio>
  </div>
</section>

Type:

ts
export type RadioChangeEvent = CustomEvent<RadioChangeEventDetail>;

export interface RadioChangeEventDetail {
  checked: boolean;
  value: string;
  name: string;
}

CSS Shadow Parts

Shadow Parts allow you to style internal elements of the radio from outside the shadow DOM using the ::part() CSS selector.

PartDescription
ag-radio-wrapperThe outer wrapper label element
ag-radio-inputThe native radio input element (visually hidden)
ag-radio-indicatorThe custom visual radio indicator (circle)
ag-radio-labelThe label text span

Customization Examples

css
ag-radio::part(ag-radio-indicator) {
  transform: scale(1.3);
  border-width: 3px;
}

ag-radio::part(ag-radio-wrapper) {
  padding: 0.5rem;
  background: #6366f1;
  border-radius: #6366f1;
}

ag-radio::part(ag-radio-label) {
  font-weight: 600;
  color: #12623e;
}

Accessibility

  • Radio uses native <input type="radio"> for proper form integration
  • Proper aria-checked state for assistive technologies
  • Keyboard navigable (Tab to focus, Arrow keys to move between radios in a group, Space to select)
  • Focus visible with customizable focus ring using design tokens
  • Always wrap radio groups with AgFieldset (ag-fieldset / ReactFieldset / VueFieldset) for proper semantic grouping and screen reader support
  • Disabled state prevents interaction and is communicated to assistive technologies

Theme Support

All themes automatically support dark mode through CSS design tokens:

  • Default theme: Alias to primary theme - uses --ag-primary (blue) which adapts to dark mode
  • Primary theme: Uses --ag-primary (blue) which adapts to dark mode
  • Success theme: Uses --ag-success (green) which adapts to dark mode
  • Monochrome theme: Uses --ag-black and --ag-white which invert in dark mode

Best Practices

  • Use name attribute: All radio buttons in a group must have the same name attribute to ensure mutual exclusivity
  • Always use AgFieldset: Wrap radio groups with AgFieldset (ag-fieldset / ReactFieldset / VueFieldset) with a descriptive legend for accessibility
  • Pre-select a default: For better UX, always have one radio pre-selected in a required group
  • Use checkboxes for multiple selections: If users can select multiple options, use checkboxes instead
  • Inline vs. stacked: Use inline layout (flex/horizontal) for 2-3 short options; use stacked (vertical) for longer labels or more options

Notes

  • Mutual exclusivity: Only one radio in a group (same name) can be selected at a time
  • Form integration: Radio buttons work seamlessly with standard HTML forms
  • Lit: Properties can be set via attributes or via property binding (e.g., .checked=${true})
  • All three implementations (Lit, React, Vue) share the same underlying styles and behavior