Choice Group
Choice Group presents a visible list of labelled options for selecting zero or one value, or zero or more values.
Complete examples
Single choice is the default:
public ?string $priority = 'routine';
public array $priorityOptions = [
['value' => 'routine', 'label' => 'Routine'],
['value' => 'urgent', 'label' => 'Urgent'],
['value' => 'critical', 'label' => 'Critical', 'disabled' => true],
];
<firstlight:choice-group
:options="$priorityOptions"
native:model="priority"
label="Priority"
helper="Choose one priority."
/>
Use multiple with a list-valued property:
public array $notifications = ['email'];
<firstlight:choice-group
:options="['email' => 'Email', 'sms' => 'SMS', 'push' => 'Push notification']"
native:model="notifications"
label="Notifications"
helper="Choose any that apply."
multiple
/>
Props
| API | Accepted type | Purpose |
|---|---|---|
options |
array |
Simple strings, a value-to-label map, or rich option arrays. Labels must contain visible text. |
value / native:model |
string, int, array, or null |
Stable selection. Use a scalar or null in single mode and a homogeneous list or null in multiple mode. |
multiple |
bool |
Enables zero-or-more selection. Defaults to false. |
variant |
list or tiles |
Full-width rows (default) or an adaptive grid of native selectable tiles. |
label |
string |
Visible field label. |
helper |
string |
Supporting text shown when there is no error. |
error |
string |
Error text and native error semantics; replaces helper text. |
required |
bool |
Communicates required state; PHP still owns validation. |
disabled |
bool |
Disables the complete group. |
a11y-label |
string |
Explicit accessibility label when a visible label is inappropriate. |
a11y-hint |
string |
Additional VoiceOver and TalkBack guidance. |
class |
string |
External EDGE layout utilities. |
Options and values
Simple strings use the same value and label:
['Routine', 'Urgent']
A map separates stable values from labels and may use all-string or all-integer keys:
['routine' => 'Routine', 'urgent' => 'Urgent']
Rich options accept value, label, optional disabled, and optional string
prefix and supporting content. These are display text, never selection values:
[
['value' => 'routine', 'label' => 'Routine'],
['value' => 'urgent', 'label' => 'Urgent', 'disabled' => true],
]
All option labels must contain visible text. Values in one group must be unique and must all be strings or all be integers.
For a favourites grid, format numbers and fees in the app and keep the bound values as stable identifiers:
<firstlight:choice-group
variant="tiles"
:options="[
['value' => 'consult', 'prefix' => '110', 'label' => 'Initial consultation', 'supporting' => '$90.00'],
['value' => 'review', 'prefix' => '116', 'label' => 'Follow-up', 'supporting' => '$45.00'],
]"
native:model="favourites"
label="Favourites"
multiple
/>
Declare public array $favourites = [];. Both variants expose prefix, label,
and supporting text as one option target. Tiles reflow for larger text rather
than forcing a fixed column count. They use the same disabled, validation,
and server-authoritative selection contract as list rows.
In single mode, tapping an unselected row proposes its value. Tapping the selected row does nothing, matching radio-choice semantics. PHP can publish null to clear the field programmatically.
In multiple mode, tapping toggles that stable value while preserving the order of the other values. null and [] both mean no selection.
Events and state timing
@change receives the complete proposed selection: a scalar in single mode and a list in multiple mode. native:model normally generates this callback for property synchronisation.
Choice Group is server-authoritative. A tap emits immediately, but selected visuals wait for PHP's next publication. Further taps are ignored while that proposal is in flight so a second proposal cannot be calculated from stale state. Rejected and programmatic publications do not emit.
An unchanged PHP response must also release the pending-interaction guard.
Host the control in FirstlightUI\NativeComponent for the
nonstreaming response-revision contract.
Revisions are not per-event acknowledgements
and do not prevent coalescing or serialize simultaneous proposals.
Use plain native:model or native:model.live; deferred blur and debounce modes are rejected.
Disabled and required behaviour
disabled prevents every row from emitting. A rich option's disabled: true affects only that row. An empty option set becomes an inert disabled group.
required is metadata rather than client-side enforcement. Validate the value in PHP and publish error when needed. Error text replaces helper text on both platforms.
Accessibility
Provide either a visible label or an explicit a11y-label. Firstlight warns during development when both are blank. Each complete row is one accessibility target with its label, selected or checked state, and disabled state. Selection is also shown with a native indicator, so it does not depend on colour alone. Rows support wrapping labels and retain at least a 44-point iOS or 48-dp Android interaction target.
Validation and failure behaviour
Contract exceptions still reject malformed options, mixed selections, and
unsupported presentation props before publication. User validation is
separate: screens that use ValidatesFields auto-bind the first MessageBag
message for the field's native:model or error-for name. An authored
error wins. required is display metadata and does not run Laravel rules.
See Validate fields. A same-typed selected
value absent from options remains unselected rather than choosing a
fallback.
Platform behaviour
iOS uses native SwiftUI option rows with trailing checkmarks. Android renders one Material 3 radio-row set for single choice and one homogeneous checkbox-row set for multiple choice. Both inherit NativePHP semantic theme tokens and preserve native focus, selection, disabled, and accessibility behaviour without exposing platform-widget props.
The tile variant composes SwiftUI checkmarked buttons in an adaptive grid on iOS and Material 3 outlined cards with radio or checkbox indicators on Android. Tile geometry remains platform-native; the selection contract is identical.
Compatibility
Choice Group supports the versions listed in the current compatibility reference and requires both native renderers in the host application plus Firstlight's screen response contract above. It does not require an unreleased runtime fix.
Screenshots
| Platform | Light | Dark |
|---|---|---|
| iOS | ![]() |
![]() |
| Android | ![]() |
![]() |



