Radio
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
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>
);
}
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:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add RadioThe 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
<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
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)
<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.
<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
<VueRadio
name="shipping"
value="standard"
label="Shipping Method"
label-text="Standard Shipping (5-7 days)"
:checked="true"
/>With Helper Text
<VueRadio
name="payment"
value="credit"
label="Payment Method"
label-text="Credit Card"
help-text="We accept all major credit cards"
/>With Validation
<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
<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
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | '' | Name attribute for the radio input (required for grouping - all radios with the same name are mutually exclusive) |
value | string | '' | Value of the radio input |
checked | boolean | false | Whether the radio is checked |
disabled | boolean | false | Whether 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) |
labelText | string | '' | 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) |
label | string | '' | Optional external label displayed above the radio (useful for groups or standalone with context) |
labelHidden | boolean | false | Visually hides the external label while keeping it accessible to screen readers |
noLabel | boolean | false | Completely removes the external label element |
required | boolean | false | Marks the radio as required. Displays an asterisk (*) after the external label |
invalid | boolean | false | Marks the radio as invalid. Sets aria-invalid and can display error message |
errorMessage | string | '' | Error message displayed when invalid. Linked via aria-describedby |
helpText | string | '' | Helper text displayed below the radio. Linked via aria-describedby |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
click | Vue: @clickReact: onClickLit: @click | MouseEvent | Fired when the radio is clicked. |
change | Vue: @changeReact: onChangeLit: @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
<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
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)
<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:
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.
| Part | Description |
|---|---|
ag-radio-wrapper | The outer wrapper label element |
ag-radio-input | The native radio input element (visually hidden) |
ag-radio-indicator | The custom visual radio indicator (circle) |
ag-radio-label | The label text span |
Customization Examples
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-checkedstate 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-blackand--ag-whitewhich invert in dark mode
Best Practices
- Use
nameattribute: All radio buttons in a group must have the samenameattribute 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