{{-- A row (or column) of radios that submit one value under one name. Props: name Field name, in HTML form. Required. label Label for the group as a whole. options Assoc array of value => label. value Fallback selection; flashed old input wins. Unlike a checkbox group this is a plain form_old() fallback, and that is correct: a radio cannot be un-picked by the user, so a missing key after a failed submit never means "the user cleared me" — it means the group was not on the submitted form, and the caller's value is still what to show. See form_old()'s docblock. required Red asterisk on the label + the `required` attribute on every radio. That differs from the checkbox group on purpose: `required` on each member of a *radio* group means "pick one of us", which is exactly what the asterisk promises, whereas on checkboxes it would demand all of them. inline true (default) lays the radios out in one wrapping row; false stacks them. group-class Extra classes for the wrapper, e.g. "col-md-6". Everything else — data-*, disabled — passes through to every radio. Checked matching is strict on the string form of the value, so a 0-valued radio is selectable and an absent selection never marks it by accident (the null == 0 trap form_selected_values() exists to close). Accessibility: a radio group is a single control, so the wrapper carries role="radiogroup" and takes its accessible name from the group label, verbatim. The checkbox group has no such role because its boxes really are independent controls. No consumer yet — this is built for the Bookings module. --- spatie/laravel-html variant. html()->radio() is called for the element but never for its state, and that is structural rather than a preference: its checked condition is `(its own old-input match) || $checked`, so "definitely not checked" is not expressible through it — whatever it decides, a caller can only ever add to. Value and checked are therefore set here, off the library's own strict resolution, the same reason checkbox-group does not use html()->checkbox(). No test can catch a regression that hands state back to html()->radio(), because on today's inputs the two resolutions agree; this paragraph is the guard. If you move state back into the builder, the failure will be silent and will show up as an option that cannot be un-checked. --}} @props([ 'name', 'label' => null, 'options' => [], 'value' => null, 'required' => false, 'inline' => true, 'groupClass' => null, ]) @php $baseId = form_field_id($name); $error = form_error_message($name); $selected = form_selected_values(form_old($name, $value)); // Each radio owns its id, so a caller supplied one is never passed on. $passthrough = form_passthrough_attributes($attributes, ['id']); $group = html()->div() ->attribute('role', 'radiogroup') ->attributeIf(filled($label), 'aria-label', (string) $label); foreach ($options as $optionValue => $optionLabel) { // Slugged from the option value, the way the checkbox group does it, // so the library has one id rule: {field}_{value}. Shared flaw, shared // with that copy: 'a b' and 'a_b' slug to the same thing and would // render duplicate ids — disambiguate when the two copies are pulled // out into a form_option_id() helper. $optionId = $baseId.'_'.preg_replace('/[^A-Za-z0-9_-]+/', '_', (string) $optionValue); // The group's error message lives once, under the group, so every // radio points at that one id. $radio = html()->radio($name) ->id($optionId) ->class('form-check-input') ->addClassIf($error !== null, 'is-invalid') ->attribute('value', (string) $optionValue) ->attributeIf(in_array((string) $optionValue, $selected, true), 'checked') ->attributeIf((bool) $required, 'required') ->attributeIf($error !== null, 'aria-invalid', 'true') ->attributeIf($error !== null, 'aria-describedby', $baseId.'_error') ->attributes($passthrough); $group = $group->addChild( html()->div() ->class('form-check') ->addClassIf((bool) $inline, 'form-check-inline') ->addChildren([ $radio, html()->label() ->class('form-check-label') ->attribute('for', $optionId) ->text((string) $optionLabel), ]) ); } @endphp {!! $group->render() !!}