Radio Rails Components

Radio button components for single-choice selections, payment methods, and configuration options. Built with Tailwind CSS and styled to match your Rails application.

Installation

1. Custom CSS

Radio button styling is included in the following forms CSS:

/* Forms */

label,
.label {
  @apply text-sm leading-6 font-medium text-neutral-700;
  @apply dark:text-neutral-100;
}

.form-input[disabled] {
  @apply cursor-not-allowed bg-neutral-200;
}

/* non-input elements (like the Stripe card form) can be styled to look like an input */
div.form-control {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #fff;
  border-width: 1px;
  padding-top: 0.5rem;
  padding-right: 0.75rem;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  font-size: 1rem;
  line-height: 1.5rem;
}

.form-control {
  @apply block w-full rounded-lg bg-white border-0 px-3 py-2 text-base leading-6 text-neutral-900 shadow-sm ring-1 ring-neutral-300 outline-hidden ring-inset placeholder:text-neutral-500 focus:ring-2 focus:ring-neutral-600 dark:bg-neutral-700 dark:text-white dark:placeholder-neutral-300 dark:ring-neutral-600 dark:focus:ring-neutral-500;
}

@media (min-width: 640px) {
  .form-control {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

.form-control[disabled] {
  @apply cursor-not-allowed bg-neutral-100 dark:bg-neutral-600;
}

.form-control.error {
  @apply border-red-400 ring-red-300 focus:ring-red-500 dark:border-red-600 dark:ring-red-500;
}

select:not([multiple]) {
  @apply w-full appearance-none rounded-lg border-0 bg-white px-3 py-2 text-base leading-6 text-neutral-900 shadow-sm ring-1 ring-neutral-300 outline-hidden ring-inset focus:ring-2 focus:ring-neutral-600;

  /* Custom dropdown arrow */
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23737373' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 0.75rem center;
  background-repeat: no-repeat;
  background-size: 1.25em 1.25em;
  padding-right: 2.5rem;
}

@media (min-width: 640px) {
  select:not([multiple]) {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

/* Dark mode styling for single select */
.dark {
  select:not([multiple]) {
    @apply dark:bg-neutral-700 dark:text-white dark:ring-neutral-600 dark:focus:ring-neutral-500;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23A1A1AA' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  }
}

select:not([multiple])[disabled] {
  @apply cursor-not-allowed bg-neutral-100 opacity-75 ring-neutral-200 dark:bg-neutral-600 dark:ring-neutral-500;
}

select[multiple] {
  @apply w-full rounded-lg rounded-r-none border-0 bg-white px-3 py-2.5 text-base leading-6 text-neutral-900 shadow-sm outline-1 -outline-offset-1 outline-neutral-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-neutral-600 dark:outline-neutral-600;
  min-height: 120px;
}

select[multiple] option {
  @apply rounded-md;
}

@media (min-width: 640px) {
  select[multiple] {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

/* Dark mode styling for multiple select */
.dark {
  select[multiple] {
    @apply dark:bg-neutral-700 dark:text-white dark:ring-neutral-600 dark:focus:ring-neutral-500;
  }
}

select[multiple][disabled] {
  @apply cursor-not-allowed bg-neutral-100 opacity-75 ring-neutral-200 dark:bg-neutral-600 dark:ring-neutral-500;
}

option {
  @apply bg-white px-3 py-2 text-sm text-neutral-900 dark:bg-neutral-700 dark:text-neutral-100;
}

option:checked {
  @apply bg-neutral-100 dark:bg-neutral-600;
}

option:hover {
  @apply bg-neutral-50 dark:bg-neutral-600;
}

.caret {
  @apply pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-neutral-800;
}

[type="checkbox"] {
  @apply size-4 cursor-pointer appearance-none rounded-sm border border-neutral-300 bg-white checked:border-neutral-700 checked:bg-neutral-800 indeterminate:border-neutral-700 indeterminate:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:border-neutral-300 disabled:bg-neutral-100 disabled:checked:bg-neutral-100 dark:border-white/20 dark:bg-neutral-800 dark:checked:border-white/20 dark:checked:bg-neutral-900 dark:indeterminate:border-neutral-500 dark:indeterminate:bg-neutral-600 dark:focus-visible:outline-neutral-200 dark:disabled:border-neutral-500 dark:disabled:bg-neutral-400 dark:disabled:checked:bg-neutral-500 forced-colors:appearance-auto;
}

[type="checkbox"]:checked {
  @apply text-white dark:text-neutral-800;
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="checkbox"]:indeterminate {
  @apply border-neutral-300 bg-neutral-800 text-white dark:border-neutral-600 dark:!bg-neutral-700 dark:text-neutral-800;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3e%3cg fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' %3e%3cline x1='10.75' y1='6' x2='1.25' y2='6'%3e%3c/line%3e%3c/g%3e%3c/svg%3e");
  background-size: 75% 75%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="checkbox"]:disabled {
  @apply cursor-not-allowed border-neutral-300 bg-neutral-300 text-neutral-400 opacity-75 hover:text-neutral-300 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:text-neutral-500;
}

[type="checkbox"]:disabled:checked {
  @apply border-neutral-300 dark:border-neutral-600 dark:bg-neutral-600;
}

[type="checkbox"]:indeterminate:hover {
  @apply bg-neutral-800 dark:border-neutral-600 dark:!bg-neutral-700;
}

[type="radio"] {
  @apply size-4 cursor-pointer appearance-none rounded-full border border-neutral-300 bg-white checked:border-neutral-700 checked:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:border-neutral-300 disabled:bg-neutral-100 disabled:checked:bg-neutral-100 dark:border-white/20 dark:bg-neutral-800 dark:checked:border-white/20 dark:checked:bg-neutral-900 dark:focus-visible:outline-neutral-200 dark:disabled:border-neutral-500 dark:disabled:bg-neutral-400 dark:disabled:checked:bg-neutral-500 forced-colors:appearance-auto;
}

[type="radio"]:checked {
  @apply text-white dark:text-neutral-800;
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="radio"]:disabled {
  @apply cursor-not-allowed border-neutral-300 bg-neutral-300 text-neutral-400 opacity-75 hover:text-neutral-300 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:text-neutral-500;
}

[type="radio"]:disabled:checked {
  @apply border-neutral-300 dark:border-neutral-600 dark:bg-neutral-600;
}

/* Datalist styling */
input[list] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

/* Replace default datalist arrow in WebKit browsers */
input[list].replace-default-datalist-arrow::-webkit-calendar-picker-indicator {
  display: none !important;
  -webkit-appearance: none !important;
}

input[list].replace-default-datalist-arrow {
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23737373' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 0.75rem center;
  background-repeat: no-repeat;
  background-size: 1.25em 1.25em;
}

/* Dark mode datalist arrow */
.dark {
  input[list].replace-default-datalist-arrow {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23A1A1AA' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  }
}

Examples

Basic Radio

Simple radio button group for payment methods, including checked and disabled states.

Payment Method
<fieldset class="space-y-4">
  <legend class="text-sm font-medium text-neutral-900 dark:text-neutral-100 mb-4">Payment Method</legend>

  <div class="flex items-center gap-2">
    <input type="radio" id="payment-card" name="payment" value="card" checked>
    <label for="payment-card" class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
      Credit Card
    </label>
  </div>

  <div class="flex items-center gap-2">
    <input type="radio" id="payment-paypal" name="payment" value="paypal">
    <label for="payment-paypal" class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
      PayPal
    </label>
  </div>

  <div class="flex items-center gap-2">
    <input type="radio" id="payment-bank" name="payment" value="bank">
    <label for="payment-bank" class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
      Bank Transfer
    </label>
  </div>

  <div class="flex items-center gap-2">
    <input type="radio" id="payment-crypto" name="payment" value="crypto" disabled>
    <label for="payment-crypto" class="text-sm font-medium text-neutral-400 dark:text-neutral-500 cursor-not-allowed opacity-50">
      Cryptocurrency (Coming Soon)
    </label>
  </div>
</fieldset>

Radio with Descriptions

Radio buttons with additional descriptive text, perfect for subscription plans or service tiers.

Subscription Plan

Perfect for individuals getting started with basic features

Advanced features for growing businesses and teams

Full-featured solution with priority support and custom integrations

<fieldset class="space-y-4 max-w-md">
  <legend class="text-sm font-medium text-neutral-900 dark:text-neutral-100 mb-4">Subscription Plan</legend>

  <div class="flex items-start gap-3">
    <input type="radio" id="plan-basic" name="subscription" value="basic" class="mt-1">
    <div class="flex-1">
      <label for="plan-basic" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">
        Basic Plan
      </label>
      <p class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">
        Perfect for individuals getting started with basic features
      </p>
    </div>
  </div>

  <div class="flex items-start gap-3">
    <input type="radio" id="plan-professional" name="subscription" value="professional" checked class="mt-1">
    <div class="flex-1">
      <label for="plan-professional" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">
        Professional Plan
      </label>
      <p class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">
        Advanced features for growing businesses and teams
      </p>
    </div>
  </div>

  <div class="flex items-start gap-3">
    <input type="radio" id="plan-enterprise" name="subscription" value="enterprise" class="mt-1">
    <div class="flex-1">
      <label for="plan-enterprise" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">
        Enterprise Plan
      </label>
      <p class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">
        Full-featured solution with priority support and custom integrations
      </p>
    </div>
  </div>
</fieldset>

Radio Cards

Card-style radio buttons for pricing plans with enhanced visual hierarchy and selection states.

Choose your plan
<fieldset class="space-y-3 max-w-sm w-full">
  <legend class="sr-only">Choose your plan</legend>

  <label for="pricing-startup" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="pricing-startup" name="pricing" value="startup" class="absolute left-4">
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Startup</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">$39 / month</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">$468 / year</span>
      </div>
    </div>
  </label>

  <label for="pricing-pro" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="pricing-pro" name="pricing" value="pro" class="absolute left-4" checked>
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Pro</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">$69 / month</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">$828 / year</span>
      </div>
    </div>
  </label>

  <label for="pricing-enterprise" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="pricing-enterprise" name="pricing" value="enterprise" class="absolute left-4">
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Enterprise</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">$129 / month</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">$1,548 / year</span>
      </div>
    </div>
  </label>
</fieldset>

Radio Cards with Icons

Radio cards with icons.

Where did you hear about us?

<fieldset class="space-y-3 max-w-md w-full">
  <p class="text-sm font-medium text-neutral-900 dark:text-neutral-100 mb-4">Where did you hear about us?</p>

  <label for="source-seo" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="source-seo" name="source" value="seo" class="absolute left-4">
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Search Engine</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">I found you on Google or other search engines</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M15.75 15.75L11.6386 11.6386"></path> <path d="M7.75 13.25C10.7875 13.25 13.25 10.7875 13.25 7.75C13.25 4.7125 10.7875 2.25 7.75 2.25C4.7125 2.25 2.25 4.7125 2.25 7.75C2.25 10.7875 4.7125 13.25 7.75 13.25Z"></path></g></svg>
        </span>
      </div>
    </div>
  </label>

  <label for="source-social" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="source-social" name="source" value="social" class="absolute left-4" checked>
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Social Media</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">I found you on Facebook, Instagram, or other social media</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M5.25,7.494c0-.48,.173-.944,.486-1.307L10,1.25h0c.854,.427,1.25,1.428,.92,2.324l-1.17,3.176h4.402c1.313,0,2.269,1.243,1.933,2.512l-1.191,4.5c-.232,.877-1.026,1.488-1.933,1.488H7.25c-1.105,0-2-.895-2-2"></path><rect x="1.75" y="6.75" width="3.5" height="8.5" rx="1" ry="1"></rect></g></svg>
        </span>
      </div>
    </div>
  </label>

  <label for="source-referral" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white">
    <input type="radio" id="source-referral" name="source" value="referral" class="absolute left-4">
    <div class="flex-1 ml-8">
      <div class="flex items-center justify-between">
        <div>
          <h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Referral</h3>
          <p class="text-xs text-neutral-500 dark:text-neutral-400">I found you through a colleague or friend</p>
        </div>
        <span class="text-sm font-medium text-neutral-900 dark:text-neutral-100">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="12.345" y1="11.75" x2="15.25" y2="11.75"></line><path d="M8.779,4.67l-.231-.313c-.283-.382-.73-.608-1.206-.608h-1.458c-.388,0-.761,.151-1.041,.42l-1.867,1.8c-.07,.067-.148,.123-.232,.167"></path><path d="M2.75,11.75h1.26c.303,0,.59,.138,.78,.374l1.083,1.349c.596,.742,1.632,.962,2.478,.525l3.274-1.693c1.111-.574,1.428-2.016,.661-3.003l-1.648-2.122"></path><path d="M15.258,6.138c-.085-.044-.163-.1-.233-.168l-1.867-1.8c-.28-.269-.653-.42-1.041-.42h-1.807c-.404,0-.791,.163-1.074,.453l-2.495,2.558c-.498,.51-.493,1.326,.011,1.83h0c.447,.447,1.15,.508,1.668,.145l2.83-1.985"></path><path d="M.75,5.25H1.75c.552,0,1,.448,1,1v6c0,.552-.448,1-1,1H.75"></path><path d="M17.25,5.25h-1c-.552,0-1,.448-1,1v6c0,.552,.448,1,1,1h1"></path></g></svg>
        </span>
      </div>
    </div>
  </label>
</fieldset>

Table of contents

Get notified when new components come out