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
Pill Group

Pill Group

Pill Group presents compact, individually shaped options for selecting zero or one value, or zero or more values. It can also be a scrolling filter row in which each option has an icon and only the selected pill shows its label.

Complete examples

Single selection is the default:

public ?string $queue = 'mine';

public array $queueOptions = [
    ['value' => 'mine', 'label' => 'Mine'],
    ['value' => 'all', 'label' => 'All'],
    ['value' => 'archived', 'label' => 'Archived', 'disabled' => true],
];
<firstlight:pill-group
    :options="$queueOptions"
    native:model="queue"
    label="Queue"
    helper="Choose the queue to include."
/>

Scrolling filter row

Give each option an icon, then add scrollable and collapse-labels. Unselected pills show only their icon; the selected pill shows its icon and label in the colour set by tone.

public ?string $filter = 'all';

public array $filterOptions = [
    ['value' => 'all', 'label' => 'All', 'icon' => 'inbox'],
    ['value' => 'unread', 'label' => 'Unread', 'icon' => 'mail'],
    ['value' => 'starred', 'label' => 'Starred', 'icon' => 'star', 'icon-ios' => 'star.fill'],
    ['value' => 'attachments', 'label' => 'Attachments', 'icon' => 'attach_file', 'icon-ios' => 'paperclip'],
];
<firstlight:pill-group
    :options="$filterOptions"
    native:model="filter"
    a11y-label="Filter"
    scrollable
    collapse-labels
    tone="success"
/>

Use multiple with an array-valued property:

public array $queues = ['mine'];
<firstlight:pill-group
    :options="$queueOptions"
    native:model="queues"
    label="Queues"
    helper="Choose any that apply."
    multiple
/>

Props

API Accepted type Purpose
options array Simple strings, a value-to-label map, or rich option arrays.
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.
scrollable bool Shows the pills in one horizontally scrolling row instead of wrapping.
collapse-labels bool Shows only the icon of unselected pills and the icon and label of selected pills. Every option needs an icon.
tone string Selected pill colour: primary (default), accent, success, warning, or danger. Unselected pills always look the same.
label string Visible field label.
helper string Supporting text below the pills.
error string Error text and native error semantics.
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:

['Mine', 'All']

A map separates stable values from labels and may use all-string or all-integer keys:

['mine' => 'Mine', 'all' => 'All']

Rich options accept value, label, and optional disabled, icon, icon-ios, and icon-android fields:

[
    ['value' => 'mine', 'label' => 'Mine', 'icon' => 'person'],
    ['value' => 'all', 'label' => 'All', 'icon' => 'groups', 'icon-ios' => 'person.3', 'disabled' => true],
]

icon is the shared fallback; icon-ios and icon-android override it per platform and also accept NativePHP IosSymbol and AndroidSymbol values. Option icons are decorative: VoiceOver and TalkBack announce the option label instead. Icons are set per option; icon on the group itself is rejected.

All values in one group must be unique and must all be strings or all be integers. In single mode, tapping the selected pill proposes null. In multiple mode, tapping toggles that stable value while preserving the order of the others. null and [] both mean no multiple selection.

Events and state timing

@change receives the complete proposed selection: a scalar or null in single mode and a list in multiple mode. native:model normally generates this callback for property synchronisation.

Pill 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 rapid second tap cannot be calculated from stale state. Programmatic updates and reconciliation do not emit.

Use plain native:model or native:model.live; deferred blur and debounce modes are rejected.

Disabled and required behaviour

disabled prevents every pill from emitting. A rich option's disabled: true affects only that pill. An empty option set becomes an inert disabled group.

required is metadata rather than client-side enforcement. It does not prevent clearing the final pill; validate the resulting value in PHP and publish error when needed.

Accessibility

Provide either a visible label or an explicit a11y-label. Firstlight warns during development when both are blank. Each pill exposes its label, selected state, and disabled state through native platform semantics. Selection also has a visible checkmark, or with collapse-labels a revealed label, so it does not depend on colour alone. Icon-only pills are announced by their option label. Pills wrap at larger text sizes, or scroll when scrollable, and retain at least a 44-point iOS or 48-dp Android interaction target. The label reveal animation is removed when Reduce Motion or system animations are turned off.

Collapsed labels hide meaning until a pill is selected, so use them for short sets with recognisable icons.

Validation and failure behaviour

Contract exceptions still reject malformed options, mixed selections, an unsupported tone, blank or invalid option icons, collapse-labels without an icon on every option, 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 selecting a fallback.

Platform behaviour

iOS uses native SwiftUI capsule buttons in a wrapping layout or a horizontal ScrollView. Android uses Material 3 FilterChip controls in a wrapping FlowRow or a horizontally scrolling row. Both inherit NativePHP semantic theme tokens and preserve native press, focus, selection, disabled, and accessibility behaviour without exposing platform-widget props.

Compatibility

Pill Group supports the versions listed in the current compatibility reference and requires both native renderers to be compiled into the host application.

Screenshots

Platform Light Dark
iOS Pill Group on iOS in light mode Pill Group on iOS in dark mode
Android Pill Group on Android in light mode Pill Group on Android in dark mode

Served from firstlightui/nativephp on GitHub.