Skip to main content
Light Dark System

Radio

<zn-radio> | ZnRadio
Since 1.0 experimental

Short summary of the component’s intended use.

Radio option
<zn-radio>Radio option</zn-radio>

Examples

Basic Radio

A basic radio button with a label.

Default option
<zn-radio>Default option</zn-radio>

Checked

Use the checked attribute to activate the radio by default.

Selected option
<zn-radio checked>Selected option</zn-radio>

Disabled

Use the disabled attribute to disable the radio.

Disabled radio
Disabled and checked
<zn-radio disabled>Disabled radio</zn-radio>
<br />
<zn-radio disabled checked>Disabled and checked</zn-radio>

Sizes

Use the size attribute to change the radio size. Available sizes are small, medium (default), and large.

Small
Medium
Large
<zn-radio size="small">Small</zn-radio>
<br />
<zn-radio size="medium">Medium</zn-radio>
<br />
<zn-radio size="large">Large</zn-radio>

Radio Groups

Radio buttons are typically used in groups where only one option can be selected. Radios with the same name will be mutually exclusive.

Red
Green
Blue
<div id="basic-radio-group">
  <zn-radio name="color" value="red">Red</zn-radio>
  <br />
  <zn-radio name="color" value="green">Green</zn-radio>
  <br />
  <zn-radio name="color" value="blue" checked>Blue</zn-radio>
</div>

Description

Add descriptive help text to radios with the description attribute. For descriptions that contain HTML, use the description slot instead.

Basic Plan
Pro Plan
Enterprise Plan
Custom pricing with dedicated support and enterprise features.
<zn-radio name="plan" description="Best for individuals and small teams">Basic Plan</zn-radio>
<br />
<zn-radio name="plan" description="Includes advanced features and priority support">Pro Plan</zn-radio>
<br />
<zn-radio name="plan">
  Enterprise Plan
  <div slot="description">
    Custom pricing with <strong>dedicated support</strong> and enterprise features.
  </div>
</zn-radio>

Label and Label Tooltip

Use the label attribute to add a form control label above the radio group. Use label-tooltip to provide additional context.

Email notifications
SMS notifications
<zn-radio label="Notification Preferences" label-tooltip="Choose how you want to be notified">
  Email notifications
</zn-radio>
<br />
<zn-radio label="Notification Preferences" label-tooltip="Choose how you want to be notified">
  SMS notifications
</zn-radio>

Contained Style

Add the contained attribute to draw a card-like container around a radio. This style is useful for giving more emphasis to radio options.

Credit Card
PayPal
Bank Transfer
<zn-radio name="payment" value="credit" description="Pay with Visa, Mastercard, or Amex" contained checked>Credit Card</zn-radio>
<br />
<zn-radio name="payment" value="paypal" description="Fast and secure PayPal checkout" contained>PayPal</zn-radio>
<br />
<zn-radio name="payment" value="bank" description="Direct bank transfer (3-5 business days)" contained>Bank Transfer</zn-radio>

Horizontal Layout

Use the horizontal attribute to apply styles relevant to radios in a horizontal layout.

Small Medium Large
<div style="display: flex; gap: 1rem;">
  <zn-radio name="size" horizontal>Small</zn-radio>
  <zn-radio name="size" horizontal checked>Medium</zn-radio>
  <zn-radio name="size" horizontal>Large</zn-radio>
</div>

Selected Content

Use the selected-content slot to display additional content (such as an input field) inside a contained radio when it is checked. The slot is unstyled by default. Use ::part(selected-content) to style the content as needed.

Standard Shipping

Estimated delivery: 5–7 business days


Express Shipping

Estimated delivery: 1–2 business days

<zn-radio name="shipping" value="standard" contained checked>
  Standard Shipping
  <div slot="selected-content">
    <p>Estimated delivery: 5-7 business days</p>
  </div>
</zn-radio>
<br />
<zn-radio name="shipping" value="express" contained>
  Express Shipping
  <div slot="selected-content">
    <p>Estimated delivery: 1-2 business days</p>
    <zn-input style="width: 280px;" label="Delivery instructions" placeholder="e.g., Leave at door"></zn-input>
  </div>
</zn-radio>
<style>
  zn-radio::part(selected-content) {
    font-size: 14px;
    font-weight: normal;
    color: #6D7176;
    margin-top: 1rem;
  }
</style>

Form Integration

Radios work seamlessly with forms and will be submitted with form data. Only the selected radio’s value will be submitted.

Choose your plan:

Free Plan
Pro Plan - $9.99/mo
Enterprise Plan - Contact us
Submit Reset
<form id="radio-form">
  <div style="margin-bottom: 1rem;">
    <strong>Choose your plan:</strong>
    <br /><br />
    <zn-radio name="subscription" value="free">Free Plan</zn-radio>
    <br />
    <zn-radio name="subscription" value="pro" checked>Pro Plan - $9.99/mo</zn-radio>
    <br />
    <zn-radio name="subscription" value="enterprise">Enterprise Plan - Contact us</zn-radio>
  </div>
  <zn-button type="submit" color="primary">Submit</zn-button>
  <zn-button type="reset" color="secondary">Reset</zn-button>
</form>

<script type="module">
  const form = document.querySelector('#radio-form');
  form.addEventListener('submit', (event) => {
    event.preventDefault();
    const formData = new FormData(form);
    const data = Object.fromEntries(formData.entries());
    alert('Form submitted: ' + JSON.stringify(data, null, 2));
  });
</script>

Required Validation

Use the required attribute to make the radio required. The form will not submit unless one of the radios in the group is selected.

Select your preferred contact method:

Email
Phone
Mail

Submit
<form id="required-form">
  <strong>Select your preferred contact method:</strong>
  <br /><br />
  <zn-radio name="contact" value="email" required>Email</zn-radio>
  <br />
  <zn-radio name="contact" value="phone" required>Phone</zn-radio>
  <br />
  <zn-radio name="contact" value="mail" required>Mail</zn-radio>
  <br /><br />
  <zn-button type="submit" color="primary">Submit</zn-button>
</form>

<script type="module">
  const form = document.querySelector('#required-form');
  form.addEventListener('submit', (event) => {
    event.preventDefault();
    alert('Form is valid!');
  });
</script>

Custom Validity

Use the setCustomValidity() method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.

Accept terms:

I accept the terms
I decline

Submit
<form class="custom-validity">
  <strong>Accept terms:</strong>
  <br /><br />
  <zn-radio name="terms" value="accept">I accept the terms</zn-radio>
  <br />
  <zn-radio name="terms" value="decline">I decline</zn-radio>
  <br /><br />
  <zn-button type="submit" color="primary">Submit</zn-button>
</form>
<script type="module">
  const form = document.querySelector('.custom-validity');
  const radios = form.querySelectorAll('zn-radio');
  const errorMessage = 'You must accept the terms to continue';

  // Set initial validity
  customElements.whenDefined('zn-radio').then(async () => {
    await Promise.all(Array.from(radios).map(r => r.updateComplete));
    radios[0].setCustomValidity(errorMessage);
  });

  // Update validity on change
  radios.forEach(radio => {
    radio.addEventListener('zn-change', () => {
      if (radio.value === 'accept' && radio.checked) {
        radios.forEach(r => r.setCustomValidity(''));
      } else if (radio.value === 'decline' && radio.checked) {
        radios.forEach(r => r.setCustomValidity(errorMessage));
      }
    });
  });

  // Wait for controls to be defined before attaching form listeners
  await Promise.all([
    customElements.whenDefined('zn-radio')
  ]).then(() => {
    form.addEventListener('submit', event => {
      event.preventDefault();
      alert('All fields are valid!');
    });
  });
</script>

External Form Association

Use the form attribute to associate the radio with a form element by ID, even if the radio is not a descendant of the form.

Submit External Form


Choose an option:

Option 1
Option 2
<form id="external-form">
  <zn-button type="submit" color="primary">Submit External Form</zn-button>
</form>

<br /><br />

<strong>Choose an option:</strong>
<br /><br />
<zn-radio form="external-form" name="external-option" value="option1" checked>Option 1</zn-radio>
<br />
<zn-radio form="external-form" name="external-option" value="option2">Option 2</zn-radio>

<script type="module">
  const form = document.querySelector('#external-form');
  form.addEventListener('submit', (event) => {
    event.preventDefault();
    const formData = new FormData(form);
    alert('Selected: ' + formData.get('external-option'));
  });
</script>

Events

Radios emit several events that you can listen to:

  • zn-change - Emitted when the checked state changes
  • zn-input - Emitted when the radio receives input
  • zn-focus - Emitted when the radio gains focus
  • zn-blur - Emitted when the radio loses focus
  • zn-invalid - Emitted when form validation fails
Try interacting with the radios:

Option 1
Option 2
Option 3
Events:
    <div>
      <strong>Try interacting with the radios:</strong>
      <br /><br />
      <zn-radio class="event-radio" name="event-demo" value="option1" checked>Option 1</zn-radio>
      <br />
      <zn-radio class="event-radio" name="event-demo" value="option2">Option 2</zn-radio>
      <br />
      <zn-radio class="event-radio" name="event-demo" value="option3">Option 3</zn-radio>
      <div id="event-output" style="margin-top: 1rem; padding: 1rem; background: #f5f5f5; border-radius: 4px;">
        <strong>Events:</strong>
        <ul id="event-list" style="margin: 0.5rem 0 0 0; padding-left: 1.5rem;"></ul>
      </div>
    </div>
    
    <script type="module">
      const radios = document.querySelectorAll('.event-radio');
      const eventList = document.querySelector('#event-list');
    
      function logEvent(eventName, detail = '') {
        const li = document.createElement('li');
        li.textContent = `${eventName}${detail ? ': ' + detail : ''}`;
        eventList.insertBefore(li, eventList.firstChild);
    
        // Keep only last 5 events
        while (eventList.children.length > 5) {
          eventList.removeChild(eventList.lastChild);
        }
      }
    
      radios.forEach(radio => {
        radio.addEventListener('zn-change', (e) => {
          logEvent('zn-change', `${e.target.value} selected`);
        });
    
        radio.addEventListener('zn-input', () => {
          logEvent('zn-input');
        });
    
        radio.addEventListener('zn-focus', (e) => {
          logEvent('zn-focus', e.target.value);
        });
    
        radio.addEventListener('zn-blur', (e) => {
          logEvent('zn-blur', e.target.value);
        });
      });
    </script>
    

    Methods

    Radios provide several methods for programmatic control:

    • click() - Simulates a click on the radio
    • focus() - Sets focus on the radio
    • blur() - Removes focus from the radio
    • checkValidity() - Checks validity without showing a message
    • reportValidity() - Checks validity and shows the browser’s validation message
    • setCustomValidity(message) - Sets a custom validation message
    • getForm() - Gets the associated form, if one exists
    Option 1
    Option 2
    Option 3

    Click Option 2 Focus Option 3 Blur All Check Validity
    <div>
      <zn-radio id="method-radio-1" name="method-demo" checked>Option 1</zn-radio>
      <br />
      <zn-radio id="method-radio-2" name="method-demo">Option 2</zn-radio>
      <br />
      <zn-radio id="method-radio-3" name="method-demo">Option 3</zn-radio>
      <br /><br />
      <zn-button id="click-btn" size="small">Click Option 2</zn-button>
      <zn-button id="focus-btn" size="small" color="info">Focus Option 3</zn-button>
      <zn-button id="blur-btn" size="small" color="secondary">Blur All</zn-button>
      <zn-button id="validate-btn" size="small" color="warning">Check Validity</zn-button>
    </div>
    
    <script type="module">
      const radio1 = document.querySelector('#method-radio-1');
      const radio2 = document.querySelector('#method-radio-2');
      const radio3 = document.querySelector('#method-radio-3');
    
      document.querySelector('#click-btn').addEventListener('click', () => {
        radio2.click();
      });
    
      document.querySelector('#focus-btn').addEventListener('click', () => {
        radio3.focus();
      });
    
      document.querySelector('#blur-btn').addEventListener('click', () => {
        document.activeElement?.blur();
      });
    
      document.querySelector('#validate-btn').addEventListener('click', () => {
        const isValid = radio1.checkValidity();
        alert('Radio group is ' + (isValid ? 'valid' : 'invalid'));
      });
    </script>
    

    Complete Example

    Here’s a comprehensive example showing a radio group with contained style, descriptions, and selected content:

    Choose Your Subscription

    Starter Plan - Free

    Includes:

    • Up to 3 users
    • 5 GB storage
    • Community support

    Professional Plan - $29/month

    Includes everything in Starter, plus:

    • Up to 25 users
    • 100 GB storage
    • Priority email support
    • Advanced analytics

    Enterprise Plan - Custom pricing

    Includes everything in Professional, plus:

    • Unlimited users
    • Unlimited storage
    • 24/7 phone support
    • Custom integrations
    • Dedicated account manager


    Continue Reset
    <form id="complete-example">
      <div style="max-width: 600px;">
        <h3 style="margin-top: 0;">Choose Your Subscription</h3>
    
        <zn-radio name="plan" value="starter" description="Perfect for getting started with basic features" contained>
          Starter Plan - Free
          <div slot="selected-content">
            <p>Includes:</p>
            <ul style="margin: 0.5rem 0; padding-left: 1.5rem;">
              <li>Up to 3 users</li>
              <li>5 GB storage</li>
              <li>Community support</li>
            </ul>
          </div>
        </zn-radio>
    
        <br />
    
        <zn-radio name="plan" value="professional" description="Advanced features for growing teams" contained checked>
          Professional Plan - $29/month
          <div slot="selected-content">
            <p>Includes everything in Starter, plus:</p>
            <ul style="margin: 0.5rem 0; padding-left: 1.5rem;">
              <li>Up to 25 users</li>
              <li>100 GB storage</li>
              <li>Priority email support</li>
              <li>Advanced analytics</li>
            </ul>
          </div>
        </zn-radio>
    
        <br />
    
        <zn-radio name="plan" value="enterprise" description="Custom solutions for large organizations" contained>
          Enterprise Plan - Custom pricing
          <div slot="selected-content">
            <p>Includes everything in Professional, plus:</p>
            <ul style="margin: 0.5rem 0; padding-left: 1.5rem;">
              <li>Unlimited users</li>
              <li>Unlimited storage</li>
              <li>24/7 phone support</li>
              <li>Custom integrations</li>
              <li>Dedicated account manager</li>
            </ul>
            <zn-input
              style="width: 100%; margin-top: 1rem;"
              label="Company name"
              placeholder="Enter your company name"
              required>
            </zn-input>
          </div>
        </zn-radio>
    
        <br /><br />
        <zn-button type="submit" color="success" style="margin-right: 0.5rem;">Continue</zn-button>
        <zn-button type="reset" color="secondary">Reset</zn-button>
      </div>
    </form>
    
    <style>
      zn-radio::part(selected-content) {
        font-size: 14px;
        font-weight: normal;
        color: #6D7176;
        margin-top: 1rem;
      }
    
      zn-radio::part(selected-content) p {
        margin: 0 0 0.5rem 0;
        font-weight: 500;
      }
    </style>
    
    <script type="module">
      const form = document.querySelector('#complete-example');
      form.addEventListener('submit', (event) => {
        event.preventDefault();
        const formData = new FormData(form);
        const plan = formData.get('plan');
        const company = formData.get('company') || 'N/A';
        alert(`Selected: ${plan}\nCompany: ${company}`);
      });
    </script>
    

    Importing

    If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

    To import this component from the CDN using a script tag:

    <script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.86/dist/components/radio/radio.js"></script>

    To import this component from the CDN using a JavaScript import:

    import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.86/dist/components/radio/radio.js';

    To import this component using a bundler:

    import '@kubex/zinc/dist/components/radio/radio.js';

    Slots

    Name Description
    (default) The radio’s label.
    description A description of the radio’s label. Serves as help text for a radio item. Alternatively, you can use the description attribute.
    selected-content Use to nest rich content (like an input) inside a selected radio item. Use only with the contained style.

    Learn more about using slots.

    Properties

    Name Description Reflects Type Default
    name The name of the radio, submitted as a name/value pair with form data. string ''
    value The current value of the radio, submitted as a name/value pair with form data. string -
    size The radio’s size. 'small' | 'medium' | 'large' 'medium'
    disabled Disables the radio. boolean false
    checked Draws the radio in a checked state. boolean false
    contained Draws a container around the radio. boolean false
    horizontal Applies styles relevant to radios in a horizontal layout. boolean false
    defaultChecked The default value of the form control. Primarily used for resetting the form control. boolean false
    form By default, form controls are associated with the nearest containing <form> element. This attribute allows you to place the form control outside a form and associate it with the form that has this id. The form must be in the same document or shadow root for this to work. string ''
    required Makes the radio a required field. boolean false
    description The radio’s help text. If you need to display HTML, use the description slot instead. string ''
    validity Gets the validity state object - -
    validationMessage Gets the validation message - -
    updateComplete A read-only promise that resolves when the component has finished updating.

    Learn more about attributes and properties.

    Events

    Name React Event Description Event Detail
    zn-blur Emitted when the radio loses focus. -
    zn-change Emitted when the checked state changes. -
    zn-focus Emitted when the radio gains focus. -
    zn-input Emitted when the radio receives input. -
    zn-invalid Emitted when the form control has been checked for validity and its constraints aren’t satisfied. -

    Learn more about events.

    Methods

    Name Description Arguments
    click() Simulates a click on the radio. -
    focus() Sets focus on the radio. options: FocusOptions
    blur() Removes focus from the radio. -
    checkValidity() Checks for validity but does not show a validation message. Returns true when valid and false when invalid. -
    getForm() Gets the associated form, if one exists. -
    reportValidity() Checks for validity and shows the browser’s validation message if the control is invalid. -
    setCustomValidity() Sets a custom validation message. The value provided will be shown to the user when the form is submitted. To clear the custom validation message, call this method with an empty string. message: string

    Learn more about methods.

    Parts

    Name Description
    base The component’s base wrapper.
    control The square container that wraps the radio’s checked state.
    control--checked Matches the control part when the radio is checked.
    checked-icon The checked icon, an <zn-icon> element.
    label The container that wraps the radio’s label.
    description The container that wraps the radio’s description.
    selected-content The container that wraps optional content that appears when a radio is checked.

    Learn more about customizing CSS parts.

    Dependencies

    This component automatically imports the following dependencies.

    • <zn-example>
    • <zn-icon>