Comparison Table
A plan-versus-feature matrix built as a real table, with readable boolean cells.
6 frameworksBeginner
Plan cards whose prices switch between monthly and annual from an accessible toggle.
<!--
CSS-only: the checkbox lives anywhere inside the group and group-has-[:checked]
swaps every monthly figure for its annual one - no JavaScript. The toggle is a
labelled checkbox so it is operable and announced without extra ARIA.
-->
<section class="group w-full">
<div class="mb-6 flex justify-center">
<label class="inline-flex cursor-pointer items-center gap-3 text-sm font-medium text-gray-700 dark:text-gray-300">
<span>Monthly</span>
<input type="checkbox" class="peer sr-only" aria-label="Bill annually" />
<span class="relative h-6 w-11 rounded-full bg-gray-200 transition-colors after:absolute after:left-0.5 after:top-0.5 after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-transform peer-checked:bg-blue-600 peer-checked:after:translate-x-5 peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-blue-600 motion-reduce:transition-none dark:bg-gray-700 dark:peer-checked:bg-blue-500"></span>
<span>Annual <span class="text-emerald-700 dark:text-emerald-400">-20%</span></span>
</label>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<section aria-label="Starter" class="flex flex-col rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-gray-900">
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Starter</h3>
<p class="mt-2 flex items-baseline gap-1">
<span class="text-3xl font-bold text-gray-900 group-has-[:checked]:hidden dark:text-gray-100">$12</span>
<span class="hidden text-3xl font-bold text-gray-900 group-has-[:checked]:inline dark:text-gray-100">$115</span>
<span class="text-sm text-gray-500 dark:text-gray-400"><span class="group-has-[:checked]:hidden">/mo</span><span class="hidden group-has-[:checked]:inline">/yr</span></span>
</p>
<ul class="mt-4 grid gap-2">
<li class="grid grid-cols-[1rem_1fr] items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span class="font-bold text-emerald-700 dark:text-emerald-400" aria-hidden="true">✓</span>
<span><span class="sr-only">Included: </span>3 projects</span>
</li>
</ul>
</section>
<section aria-label="Pro" class="flex flex-col rounded-xl border border-blue-600 bg-white p-5 dark:border-blue-400 dark:bg-gray-900">
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Pro</h3>
<p class="mt-2 flex items-baseline gap-1">
<span class="text-3xl font-bold text-gray-900 group-has-[:checked]:hidden dark:text-gray-100">$29</span>
<span class="hidden text-3xl font-bold text-gray-900 group-has-[:checked]:inline dark:text-gray-100">$278</span>
<span class="text-sm text-gray-500 dark:text-gray-400"><span class="group-has-[:checked]:hidden">/mo</span><span class="hidden group-has-[:checked]:inline">/yr</span></span>
</p>
<ul class="mt-4 grid gap-2">
<li class="grid grid-cols-[1rem_1fr] items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span class="font-bold text-emerald-700 dark:text-emerald-400" aria-hidden="true">✓</span>
<span><span class="sr-only">Included: </span>Unlimited projects</span>
</li>
</ul>
</section>
</div>
</section>| Prop | Type | Default | Description |
|---|---|---|---|
plansrequired | TogglePlan[] | - | The array of items to render. |
currency | string | '$' | Currency |
className | string | - | Additional classes merged onto the root element. |
The React and TypeScript variants use a real radiogroup (two radios, not two unlabelled buttons) so the billing choice is announced and arrow-navigable; the Tailwind variant does the same job with a single labelled checkbox and `group-has-[:checked]` so it swaps every price with no JavaScript. Mark a plan `featured` to accent its border, and the grid steps one -> two -> three columns as space allows.