Skip to main content
← Back to table of contents

Components Reference

Complete API reference for custom form components shipped with Filament Flex Fields (janczakb/filament-flex-fields). This document covers custom UI components (form fields, table columns, and layout/schema components). Standard Filament fields (TextInput, Select, etc.) are mapped via FieldType and are not described here.

Table of contents

Part I — Shared concepts

  1. Overview
  2. Documentation conventions
  3. Control size
  4. Inherited Filament field API
  5. Assets & playground
  6. Rich card option shape
  7. Rich select option shape
  8. Dual listbox option shape

Part II — Components

  1. FlexTextInput
  2. FlexTextareaField
  3. SelectField
  4. UserSelect
  5. UserColumn
  6. DualListboxField
  7. PriceRangeField
  8. CreditCardField
  9. PhoneField
  10. SignatureField
  11. MapPickerField
  12. AddressAutocompleteField
  13. ChoiceCards
  14. ChoiceCheckboxCards
  15. FlexChecklist
  16. FlexRadiolist
  17. MatrixChoiceField
  18. SwitchField
  19. CurrencyField
  20. CountryField
  21. TimezoneField
  22. Date & time fields
  23. FlexVerificationCode
  24. AudioField
  25. VoiceNoteRecorderField
  26. VideoField
  27. FlexFileUpload & FlexImageUpload
  28. ColorSwatchField
  29. FlexColorPickerField
  30. NumberStepper
  31. FlexSlider
  32. SegmentTabs
  33. SegmentControl
  34. TrackSlider
  35. TrafficSplit
  36. RatingField
  37. RatingColumn
  38. IconColumn
  39. CoverCard
  40. ProgressBar
  41. ProgressCircle
  42. ItemCard
  43. ItemCardGroup
  44. ItemCardStack
  45. Layout components — quick comparison
  46. Form layout patterns
  47. SlugField & TitleSlugField
  48. TranslatableFields

Part I — Shared concepts

Overview

Filament Flex Fields provides modern SaaS-inspired form controls with a unified design language:
  • Shared size scale (sm, md, lg)
  • Shared CSS tokens (--fff-* in resources/css/base.css and modular bundles under resources/css/)
  • Filament field wrapper integration (labels, validation errors, helper text)
  • Optional playground page for visual QA
All custom components extend Filament\Forms\Components\Field, or extend a native Filament input (TextInput, Textarea, Select) while replacing the view with a styled package template.

Documentation conventions

Each component section documents four layers of API surface:
LayerDescription
Chainable config methodsFluent ->method() calls on the component class. Accept scalars, Closure, or Filament utility injection.
Inherited APIsMethods from parent Filament classes (Field, TextInput, Select, traits) that work unchanged. Listed briefly where relevant.
Public helper methodsNon-chainable getters and utilities used by Blade views, tests, or custom extensions. Documented per component when they expose stable behaviour.
FlexField schema keysSnake-case keys in field config arrays passed to FlexFieldFormBuilder. Map to chainable methods. Keys not yet wired in the builder may still be valid when configuring fields manually.

Control size

Most components accept a size() method.
ValueEnum constantTrack height
smControlSize::Sm32px
mdControlSize::Md40px (default)
lgControlSize::Lg48px
use Bjanczak\FilamentFlexFields\Enums\ControlSize;

->size('md')
->size(ControlSize::Lg)
Package defaults live in config/filament-flex-fields.php under the ui key, for example:
Config keyAffects
flex_text_input_sizeFlexTextInput
flex_text_input_variantFlexTextInput
flex_textarea_sizeFlexTextareaField
flex_textarea_variantFlexTextareaField
select_sizeSelectField
select_variantSelectField
dual_listbox_sizeDualListboxField
dual_listbox_variantDualListboxField
price_range_sizePriceRangeField
price_range_variantPriceRangeField
credit_card_sizeCreditCardField
credit_card_variantCreditCardField
number_stepper_sizeNumberStepper
segment_sizeSegmentControl
slider_sizeTrackSlider
switch_sizeSwitchField
rating_sizeRatingField
Choice card components fall back to choice_cards_size and choice_cards_variant when used via FlexFieldFormBuilder (add these keys to config if needed).

Inherited Filament field API

Every component in Part II inherits the standard Filament Field API. Common methods:
MethodDescription
label()Field label rendered by the Filament wrapper
helperText()Helper text below the field
hint()Visible hint text next to the label
hintIcon()Icon next to the label with optional tooltip
hintIconTooltip()Tooltip text for hintIcon()
hintAction()Clickable action next to the label
placeholder()Placeholder where applicable
required()Marks the field as required
disabled()Disables the entire field
default()Default state value
live()Live / reactive updates
dehydrated()Whether the value is saved to form state
hidden() / visible()Conditional visibility
rule() / rules()Additional validation rules
afterStateUpdated()Callback after state changes
Validation errors are displayed below the component using the standard Filament field wrapper. All configuration methods accept a Closure for dynamic values and support Filament utility injection.

Livewire wire:ignore strategy (map & heavy Alpine fields)

Several interactive fields (MapPickerField, AddressAutocompleteField, SelectField, PhoneField, CountryField, and others) wrap third-party or Filament Alpine trees inside wire:ignore so Livewire does not destroy DOM that Alpine manages.
ConcernStrategy
State syncPass Livewire state through $wire.$entangle('statePath') in x-data — Alpine owns the UI, Livewire owns persistence.
Config changesAdd a wire:key hash over read-only/disabled/config props (token, fields(), storeFormat(), size, variant). When config changes, Livewire remounts the ignored subtree with fresh Alpine boot data.
Server updatesAvoid ->set('data.field') on ignored fragments in tests; change upstream props or entangled state instead.
Dropdowns / mapsTeleport menus to body, use shared overlay coordinator (fffOverlays) so only one menu stays open. teleported-menu.css raises z-index when a Filament modal is open (:has(.fi-modal.fi-modal-open)).
Example (MapPickerField / AddressAutocompleteField):
<div
    wire:ignore
    wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled, $field->getFields(), ...])), 0, 64) }}"
    x-data="mapPickerFormComponent({ state: $wire.$entangle('...') })"
>
When adding new map-like or Mapbox-backed fields, follow the same pattern: $entangle for state, wire:key for remounts, x-load ES module for JS.

Assets & playground

CSS is split into bundles:
Asset IDFileLoading
flex-fields-coreresources/dist/css/core.cssAlways loaded (tokens, switches, item cards, hold-confirm actions, shared layout)
flex-fields-playgroundresources/dist/css/playground.cssBase playground chrome only
flex-fields-playground-{slug}resources/dist/css/playground-{slug}.cssPer-slug playground bundle (e.g. playground-phone-field.css)
flex-fields-{component}resources/dist/css/{component}.cssLazy — injected by form field blades and CoverCard via load-stylesheetemit-assets

JavaScript (tiered chunks)

All Alpine components are compiled together in a single esbuild build with splitting: true. If two fields import the same module from resources/js/core/ or resources/js/support/, the code is split into a shared chunk — loaded once and cached by the browser.
OutputRole
resources/dist/components/{component}.jsThin entry — Alpine x-data factory for the component
resources/dist/components/flex-fields-{name}-{hash}.jsShared modules with semantic names (e.g. flex-fields-emoji-*.js)
resources/dist/components/alpine-manifest.jsonComponent-to-chunk map + __chunk_modules__ metadata

Currently shared chunks (auto-generated from build)

Source modulesComponentsPurpose / Example
core/shared-emoji-picker.jsflex-text-input, flex-textareaEmoji picker (~45 KB)
core/audio-waveform.js, format-time.js, waveform-bars.js, audio-playback.jsaudio-field, voice-note-recorder-fieldWaveform, playback, time formatting
core/dynamic-bars.jsprice-range, audio-field, voice-note-recorder-fieldWaveform / price histogram bars
support/mapbox-geocoding.jsmap-picker, address-autocompleteMapbox geocoding integration
core/searchable-select-menu.jscountry-field, timezone-field, currency-fieldCommon dropdown overlay shell
libphonenumber-js (lazy import())phone-fieldPhone validation (~190 KB, loaded on demand)
Components without entries in the manifest (e.g. rating-field, dual-listbox) do not share code with other components — their entire logic remains inside their thin entry (which is expected). Modules used by only a single field (e.g. core/date-time/* used only by flex-date-time-field, nouislider used only by flex-slider) remain inside their entries until another component starts importing them.

Preload & delivery

Every blade template rendering a component stylesheet registers CSS and Alpine chunks in request-scoped queues (FlexFieldStylesheetQueue, FlexFieldAlpineQueue). load-stylesheet immediately emits emit-assets:
  • Full page@push('styles') into Filament @stack('styles') in &lt;head&gt;.
  • Livewire partial — inline &lt;link&gt; / modulepreload tags plus a hidden data-fff-asset-batch marker for the injector.
queued-stylesheets flushes any remaining pending() queues at STYLES_AFTER and BODY_END. At HEAD_END, critical-stylesheet-preloads may emit teleported-menu only when FlexFieldStylesheetQueue has already registered a component that depends on it (e.g. table columns in setUp()). Form fields enqueue during body render via load-stylesheetemit-assets instead. HoldConfirmAction preloads its Alpine entry via @push modulepreload in hold-confirm.blade.php when the action renders — not globally in HEAD_END.

Asset injector (SPA / modals)

flex-field-asset-injector.js (registered Filament JS asset at SCRIPTS_AFTER) handles:
  • href deduplication (normalizeAssetUrl, Map indices, in-flight promise cache),
  • loading missing CSS and Alpine chunks from morph batches,
  • modal FOUC prevention (morph.updating / morph.updated, fff-flex-fields-assets-pending / ready classes),
  • protected links (data-fff-stylesheet, data-fff-alpine-chunk, data-fff-playground-bundle).
Components without custom CSS but requiring JS (e.g. rating-field) load their chunks dynamically via ESM import inside x-load — without explicit preloading, since their manifest entry is empty. After modifying JavaScript (including the injector):
npm run build:js
php artisan filament:assets
Table column styles (UserColumn, RatingColumn, IconColumn, etc.) are lazy-loaded per column via FlexFieldStylesheetQueue::enqueueFor() in the column setUp() — not via load-stylesheet in table cell blades. The queued-stylesheets partial (render hooks at STYLES_AFTER and BODY_END) flushes pending bundles once; markStylesheetsEmitted() prevents duplicates. Playground slug pages use per-slug bundles with suppressForPlaygroundBundle() so lazy CSS is not injected twice. Playground CSS uses base playground.css plus per-slug playground-{slug}.css bundles pushed via playground-page-stylesheets. After changing package CSS or JS:
# Inside the package directory
npm run build:css          # core + playground + per-component bundles
npm run build              # CSS + JS

# Inside the Laravel application
php artisan filament:assets
Enable the playground (local by default):
FLEX_FIELDS_PLAYGROUND=true
The playground renders live examples of every custom component and variant.

Rich card option shape

Used by ChoiceCards and ChoiceCheckboxCards via options().

Simple option

'pro' => 'Pro plan',

Rich option

'starter' => [
    'label' => 'Starter',
    'description' => 'For individuals and small projects',
    'price' => '$5',
    'price_suffix' => '/mo',
    'meta' => '4,200 subscribers',
    'icon' => 'heroicon-o-sparkles',
    'badge' => 'Popular',
    'badge_color' => 'success',
    'disabled' => false,
],
KeyTypeDescription
labelstringCard title. Falls back to the option key.
descriptionstring|nullSecondary text under the title.
pricestring|nullPrice line. Alias: value.
price_suffixstring|nullSuffix after price, e.g. /mo. Alias: suffix.
metastring|nullExtra footer text (monospace in UI).
iconstring|nullHeroicon name. Rendered in media and featured layouts.
badgestring|nullBadge label. Rendered in featured layout.
badge_colorstringBadge color token. Default: success.
disabledboolDisables this option. Combined with disabledOptions().

Rich select option shape

Used by SelectField when options use a rich array shape or when richOptions() / optionLayout('grid') is enabled.

Simple option

'livewire' => 'Livewire',

Rich option

'laravel' => [
    'label' => 'Laravel',
    'description' => 'The PHP framework for web artisans',
    'icon' => 'heroicon-o-bolt',
    'image' => null,
    'badge' => 'Popular',
    'badge_color' => 'success',
    'disabled' => false,
],
KeyTypeDescription
labelstringOption title. Falls back to the option key.
descriptionstring|nullSecondary line in list layout.
iconstring|BackedEnum|Htmlable|nullHeroicon rendered in the option row.
imagestring|nullImage URL for grid layout cards.
badgestring|nullBadge label in list layout.
badge_colorstringFilament color token for the badge. Default: primary.
disabledboolDisables this option.
Option groups use nested arrays: 'Backend' =&gt; ['laravel' =&gt; 'Laravel', ...].

Dual listbox option shape

Used by DualListboxField via options().

Simple option

'read' => 'Read',

Rich option

'write' => [
    'label' => 'Write',
    'description' => 'Create and update records',
    'disabled' => false,
],
KeyTypeDescription
labelstringItem label. Falls back to the option key.
descriptionstring|nullSecondary text under the label.
disabledboolDisables this option. Combined with disabledOptions().