Skip to main content
Firstlight

Firstlight / Docs

Recipes for the screens a forms app needs, setup for your coding agent, Laravel on native screens, and accessibility.

View source
Choice Group

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 Choice Group on iOS in light mode Choice Group on iOS in dark mode
Android Choice Group on Android in light mode Choice Group on Android in dark mode

Served from firstlightui/nativephp on GitHub.