Radio Group

A set of checkable buttons where no more than one of the buttons can be checked at a time.

Loading...
<twig:RadioGroup class="w-fit">
    <div class="flex items-center gap-3">
        <twig:RadioGroup:Item id="r1" name="spacing" value="default" />
        <twig:Label for="r1">Default</twig:Label>
    </div>
    <div class="flex items-center gap-3">
        <twig:RadioGroup:Item id="r2" name="spacing" value="comfortable" checked />
        <twig:Label for="r2">Comfortable</twig:Label>
    </div>
    <div class="flex items-center gap-3">
        <twig:RadioGroup:Item id="r3" name="spacing" value="compact" />
        <twig:Label for="r3">Compact</twig:Label>
    </div>
</twig:RadioGroup>

Installation

bin/console ux:install radio-group --kit shadcn

That's it!

Install the following Composer dependencies:

composer require tales-from-a-dev/twig-tailwind-extra:^1.0.0

Copy the following file(s) into your Symfony app:

templates/components/RadioGroup.html.twig
{# @block content The radio items, typically multiple `RadioGroup:Item` components with `Label` #}
<div
    role="radiogroup"
    class="{{ ('grid w-full gap-2 ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes.defaults({
        'data-slot': 'radio-group',
    }) }}
>
    {%- block content %}{% endblock -%}
</div>
templates/components/RadioGroup/Item.html.twig
{# @prop name string The name shared by all radio inputs in the same group #}
{# @prop value string The value submitted when this item is selected #}
{%- props name, value -%}
<label class="{{ ('relative inline-flex size-4 shrink-0 items-center justify-center cursor-pointer group-data-[disabled=true]/field:cursor-not-allowed group-data-[disabled=true]/field:opacity-50 ' ~ attributes.render('class'))|tailwind_merge }}">
    <input
        type="radio"
        name="{{ name }}"
        value="{{ value }}"
        class="peer sr-only"
        {{ attributes.defaults({
            'data-slot': 'radio-group-item',
        }) }}
    >
    <span class="flex size-4 cursor-pointer items-center justify-center rounded-full border border-input outline-none peer-focus-visible:border-ring peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50 peer-disabled:cursor-not-allowed peer-aria-invalid:border-destructive peer-aria-invalid:ring-[3px] peer-aria-invalid:ring-destructive/20 dark:bg-input/30 dark:peer-aria-invalid:border-destructive/50 dark:peer-aria-invalid:ring-destructive/40 peer-checked:border-primary peer-checked:bg-primary dark:peer-checked:bg-primary"></span>
    <span class="pointer-events-none absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 scale-0 rounded-full bg-primary-foreground transition-transform peer-checked:scale-100"></span>
</label>

Happy coding!

Usage

<twig:RadioGroup class="w-fit">
    <div class="flex items-center gap-3">
        <twig:RadioGroup:Item id="option-a" name="option" value="a" checked />
        <twig:Label for="option-a">Option A</twig:Label>
    </div>
    <div class="flex items-center gap-3">
        <twig:RadioGroup:Item id="option-b" name="option" value="b" />
        <twig:Label for="option-b">Option B</twig:Label>
    </div>
</twig:RadioGroup>

Examples

Description

Loading...
<twig:RadioGroup class="w-fit">
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="desc-r1" name="density" value="default" />
        <twig:Field:Content>
            <twig:Field:Label for="desc-r1">Default</twig:Field:Label>
            <twig:Field:Description>Standard spacing for most use cases.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="desc-r2" name="density" value="comfortable" checked />
        <twig:Field:Content>
            <twig:Field:Label for="desc-r2">Comfortable</twig:Field:Label>
            <twig:Field:Description>More space between elements.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="desc-r3" name="density" value="compact" />
        <twig:Field:Content>
            <twig:Field:Label for="desc-r3">Compact</twig:Field:Label>
            <twig:Field:Description>Minimal spacing for dense layouts.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
</twig:RadioGroup>

Choice Card

Loading...
<twig:RadioGroup class="max-w-sm">
    <twig:Field:Label for="plus-plan" class="border border-input rounded-md has-[:checked]:bg-primary/5 has-[:checked]:border-primary/20 dark:has-[:checked]:bg-primary/10">
        <twig:Field orientation="horizontal" class="p-3">
            <twig:Field:Content>
                <twig:Field:Title>Plus</twig:Field:Title>
                <twig:Field:Description>For individuals and small teams.</twig:Field:Description>
            </twig:Field:Content>
            <twig:RadioGroup:Item id="plus-plan" name="plan" value="plus" checked />
        </twig:Field>
    </twig:Field:Label>
    <twig:Field:Label for="pro-plan" class="border border-input rounded-md has-[:checked]:bg-primary/5 has-[:checked]:border-primary/20 dark:has-[:checked]:bg-primary/10">
        <twig:Field orientation="horizontal" class="p-3">
            <twig:Field:Content>
                <twig:Field:Title>Pro</twig:Field:Title>
                <twig:Field:Description>For growing businesses.</twig:Field:Description>
            </twig:Field:Content>
            <twig:RadioGroup:Item id="pro-plan" name="plan" value="pro" />
        </twig:Field>
    </twig:Field:Label>
    <twig:Field:Label for="enterprise-plan" class="border border-input rounded-md has-[:checked]:bg-primary/5 has-[:checked]:border-primary/20 dark:has-[:checked]:bg-primary/10">
        <twig:Field orientation="horizontal" class="p-3">
            <twig:Field:Content>
                <twig:Field:Title>Enterprise</twig:Field:Title>
                <twig:Field:Description>For large teams and enterprises.</twig:Field:Description>
            </twig:Field:Content>
            <twig:RadioGroup:Item id="enterprise-plan" name="plan" value="enterprise" />
        </twig:Field>
    </twig:Field:Label>
</twig:RadioGroup>

Fieldset

Loading...
<twig:Field:Set class="w-full max-w-xs">
    <twig:Field:Legend variant="label">Subscription Plan</twig:Field:Legend>
    <twig:Field:Description>Yearly and lifetime plans offer significant savings.</twig:Field:Description>
    <twig:RadioGroup class="gap-3">
        <twig:Field orientation="horizontal">
            <twig:RadioGroup:Item id="plan-monthly" name="subscription" value="monthly" checked />
            <twig:Field:Label for="plan-monthly" class="font-normal">Monthly ($9.99/month)</twig:Field:Label>
        </twig:Field>
        <twig:Field orientation="horizontal">
            <twig:RadioGroup:Item id="plan-yearly" name="subscription" value="yearly" />
            <twig:Field:Label for="plan-yearly" class="font-normal">Yearly ($99.99/year)</twig:Field:Label>
        </twig:Field>
        <twig:Field orientation="horizontal">
            <twig:RadioGroup:Item id="plan-lifetime" name="subscription" value="lifetime" />
            <twig:Field:Label for="plan-lifetime" class="font-normal">Lifetime ($299.99)</twig:Field:Label>
        </twig:Field>
    </twig:RadioGroup>
</twig:Field:Set>

Disabled

Loading...
<twig:RadioGroup class="w-fit">
    <twig:Field orientation="horizontal" data-disabled="true" class="opacity-50">
        <twig:RadioGroup:Item id="disabled-1" name="disabled-example" value="option1" disabled />
        <twig:Field:Label for="disabled-1" class="font-normal">Disabled</twig:Field:Label>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="disabled-2" name="disabled-example" value="option2" checked />
        <twig:Field:Label for="disabled-2" class="font-normal">Option 2</twig:Field:Label>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="disabled-3" name="disabled-example" value="option3" />
        <twig:Field:Label for="disabled-3" class="font-normal">Option 3</twig:Field:Label>
    </twig:Field>
</twig:RadioGroup>

Invalid

Loading...
<twig:Field:Set class="w-full max-w-xs">
    <twig:Field:Legend variant="label">Notification Preferences</twig:Field:Legend>
    <twig:Field:Description>Choose how you want to receive notifications.</twig:Field:Description>
    <twig:RadioGroup>
        <twig:Field orientation="horizontal" data-invalid="true">
            <twig:RadioGroup:Item id="invalid-email" name="notifications" value="email" checked aria-invalid="true" />
            <twig:Field:Label for="invalid-email" class="font-normal">Email only</twig:Field:Label>
        </twig:Field>
        <twig:Field orientation="horizontal" data-invalid="true">
            <twig:RadioGroup:Item id="invalid-sms" name="notifications" value="sms" aria-invalid="true" />
            <twig:Field:Label for="invalid-sms" class="font-normal">SMS only</twig:Field:Label>
        </twig:Field>
        <twig:Field orientation="horizontal" data-invalid="true">
            <twig:RadioGroup:Item id="invalid-both" name="notifications" value="both" aria-invalid="true" />
            <twig:Field:Label for="invalid-both" class="font-normal">Both Email &amp; SMS</twig:Field:Label>
        </twig:Field>
    </twig:RadioGroup>
</twig:Field:Set>

RTL

Loading...
<twig:RadioGroup class="w-fit" dir="rtl">
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="r1-rtl" name="density-rtl" value="default" dir="rtl" />
        <twig:Field:Content>
            <twig:Field:Label for="r1-rtl" dir="rtl">افتراضي</twig:Field:Label>
            <twig:Field:Description dir="rtl">تباعد قياسي لمعظم حالات الاستخدام.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="r2-rtl" name="density-rtl" value="comfortable" checked dir="rtl" />
        <twig:Field:Content>
            <twig:Field:Label for="r2-rtl" dir="rtl">مريح</twig:Field:Label>
            <twig:Field:Description dir="rtl">مساحة أكبر بين العناصر.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
    <twig:Field orientation="horizontal">
        <twig:RadioGroup:Item id="r3-rtl" name="density-rtl" value="compact" dir="rtl" />
        <twig:Field:Content>
            <twig:Field:Label for="r3-rtl" dir="rtl">مضغوط</twig:Field:Label>
            <twig:Field:Description dir="rtl">تباعد أدنى للتخطيطات الكثيفة.</twig:Field:Description>
        </twig:Field:Content>
    </twig:Field>
</twig:RadioGroup>

API Reference

RadioGroup

Block Description
content The radio items, typically multiple RadioGroup:Item components with Label

RadioGroup:Item

Prop Type Description
name string The name shared by all radio inputs in the same group
value string The value submitted when this item is selected