Radio Groups
Ported from HyperUI’s Radio Groups — see Attribution.
Includes a dark: variant — toggle the site theme (top right) to preview it.
class RadioGroups1Example extends StatefulWidget { const RadioGroups1Example({super.key});
@override State<RadioGroups1Example> createState() => _RadioGroups1ExampleState();}
class _RadioGroups1ExampleState extends State<RadioGroups1Example> { var _selected = 0;
@override Widget build(BuildContext context) { return Style( 'mx-auto max-w-lg p-6', child: Style( 'space-y-3', children: [ const Style('sr-only', child: Text('Delivery')), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 0), child: Style( 'flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600 dark:border-gray-600 dark:bg-gray-900 dark:hover:bg-gray-800', cursor: SystemMouseCursors.click, children: [ const Style('text-gray-700 dark:text-gray-200', child: Text('Standard')), const Style('text-gray-900 dark:text-white', child: Text('Free')), Style('sr-only', checked: _selected == 0, child: const SizedBox.shrink()), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 1), child: Style( 'flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600 dark:border-gray-600 dark:bg-gray-900 dark:hover:bg-gray-800', cursor: SystemMouseCursors.click, children: [ const Style('text-gray-700 dark:text-gray-200', child: Text('Next Day')), const Style('text-gray-900 dark:text-white', child: Text('£9.99')), Style('sr-only', checked: _selected == 1, child: const SizedBox.shrink()), ], ), ), ], ), ); }}<fieldset class="space-y-3"> <legend class="sr-only">Delivery</legend>
<div> <label for="DeliveryStandard" class="flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600" > <p class="text-gray-700">Standard</p>
<p class="text-gray-900">Free</p>
<input type="radio" name="DeliveryOption" value="DeliveryStandard" id="DeliveryStandard" class="sr-only" checked /> </label> </div>
<div> <label for="DeliveryPriority" class="flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600" > <p class="text-gray-700">Next Day</p>
<p class="text-gray-900">£9.99</p>
<input type="radio" name="DeliveryOption" value="DeliveryPriority" id="DeliveryPriority" class="sr-only" /> </label> </div></fieldset>Base with input
Section titled “Base with input”Includes a dark: variant — toggle the site theme (top right) to preview it.
class RadioGroups2Example extends StatefulWidget { const RadioGroups2Example({super.key});
@override State<RadioGroups2Example> createState() => _RadioGroups2ExampleState();}
class _RadioGroups2ExampleState extends State<RadioGroups2Example> { var _selected = 0;
@override Widget build(BuildContext context) { return Style( 'mx-auto max-w-lg p-6', child: Style( 'space-y-3', children: [ const Style('sr-only', child: Text('Delivery')), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 0), child: Style( 'flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600 dark:border-gray-600 dark:bg-gray-900 dark:hover:bg-gray-800', cursor: SystemMouseCursors.click, children: [ Style.empty( children: [ const Style('text-gray-700 dark:text-gray-200', child: Text('Standard')), const Style('text-gray-900 dark:text-white', child: Text('Free')), ], ), Style( 'size-5 border-gray-300 dark:border-gray-600 dark:bg-gray-900 dark:ring-offset-gray-900 dark:checked:bg-blue-600', formControl: true, formControlRadio: true, checked: _selected == 0, ), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 1), child: Style( 'flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600 dark:border-gray-600 dark:bg-gray-900 dark:hover:bg-gray-800', cursor: SystemMouseCursors.click, children: [ Style.empty( children: [ const Style('text-gray-700 dark:text-gray-200', child: Text('Next Day')), const Style('text-gray-900 dark:text-white', child: Text('£9.99')), ], ), Style( 'size-5 border-gray-300 dark:border-gray-600 dark:bg-gray-900 dark:ring-offset-gray-900 dark:checked:bg-blue-600', formControl: true, formControlRadio: true, checked: _selected == 1, ), ], ), ), ], ), ); }}<fieldset class="space-y-3"> <legend class="sr-only">Delivery</legend>
<div> <label for="DeliveryStandard" class="flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600" > <div> <p class="text-gray-700">Standard</p>
<p class="text-gray-900">Free</p> </div>
<input type="radio" name="DeliveryOption" value="DeliveryStandard" id="DeliveryStandard" class="size-5 border-gray-300" checked /> </label> </div>
<div> <label for="DeliveryPriority" class="flex items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50 has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600" > <div> <p class="text-gray-700">Next Day</p>
<p class="text-gray-900">£9.99</p> </div>
<input type="radio" name="DeliveryOption" value="DeliveryPriority" id="DeliveryPriority" class="size-5 border-gray-300" /> </label> </div></fieldset>Color variant
Section titled “Color variant”class RadioGroups3Example extends StatefulWidget { const RadioGroups3Example({super.key});
@override State<RadioGroups3Example> createState() => _RadioGroups3ExampleState();}
class _RadioGroups3ExampleState extends State<RadioGroups3Example> { var _selected = 0;
@override Widget build(BuildContext context) { return Style( 'mx-auto max-w-lg p-6', child: Style( 'flex flex-wrap gap-3', children: [ const Style('sr-only', child: Text('Color')), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 0), child: Style( 'block size-8 rounded-full bg-black shadow-sm has-checked:ring-2 has-checked:ring-black has-checked:ring-offset-2', cursor: SystemMouseCursors.click, children: [ Style('sr-only', checked: _selected == 0, child: const SizedBox.shrink()), const Style('sr-only', child: Text('Black')), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 1), child: Style( 'block size-8 rounded-full bg-red-500 shadow-sm has-checked:ring-2 has-checked:ring-red-500 has-checked:ring-offset-2', cursor: SystemMouseCursors.click, children: [ Style('sr-only', checked: _selected == 1, child: const SizedBox.shrink()), const Style('sr-only', child: Text('Red')), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 2), child: Style( 'block size-8 rounded-full bg-blue-500 shadow-sm has-checked:ring-2 has-checked:ring-blue-500 has-checked:ring-offset-2', cursor: SystemMouseCursors.click, children: [ Style('sr-only', checked: _selected == 2, child: const SizedBox.shrink()), const Style('sr-only', child: Text('Blue')), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _selected = 3), child: Style( 'block size-8 rounded-full bg-amber-500 shadow-sm has-checked:ring-2 has-checked:ring-amber-500 has-checked:ring-offset-2', cursor: SystemMouseCursors.click, children: [ Style('sr-only', checked: _selected == 3, child: const SizedBox.shrink()), const Style('sr-only', child: Text('Gold')), ], ), ), ], ), ); }}<fieldset class="flex flex-wrap gap-3"> <legend class="sr-only">Color</legend>
<label for="ColorBlack" class="block size-8 rounded-full bg-black shadow-sm has-checked:ring-2 has-checked:ring-black has-checked:ring-offset-2" > <input type="radio" name="ColorOption" value="ColorBlack" id="ColorBlack" class="sr-only" checked />
<span class="sr-only">Black</span> </label>
<label for="ColorRed" class="block size-8 rounded-full bg-red-500 shadow-sm has-checked:ring-2 has-checked:ring-red-500 has-checked:ring-offset-2" > <input type="radio" name="ColorOption" value="ColorRed" id="ColorRed" class="sr-only" />
<span class="sr-only">Red</span> </label>
<label for="ColorBlue" class="block size-8 rounded-full bg-blue-500 shadow-sm has-checked:ring-2 has-checked:ring-blue-500 has-checked:ring-offset-2" > <input type="radio" name="ColorOption" value="ColorBlue" id="ColorBlue" class="sr-only" />
<span class="sr-only">Blue</span> </label>
<label for="ColorGold" class="block size-8 rounded-full bg-amber-500 shadow-sm has-checked:ring-2 has-checked:ring-amber-500 has-checked:ring-offset-2" > <input type="radio" name="ColorOption" value="ColorGold" id="ColorGold" class="sr-only" />
<span class="sr-only">Gold</span> </label></fieldset>