Skip to main content
← Back to Table of Contents

Summary

Multiple choice grid (matrix / survey table): row labels on the left, column headers on top, radio or checkbox in each cell. Gray inset frame with white body panel. Per-row validation and reactive conditional disabling (no live() required).
Use matrixColumns() — not columns() — because columns() is reserved by Filament layout grids.

Full example

Radio mode (one answer per row — survey / mood matrix):

Row option shape

Each key in rows() is stored in the database. Value can be a plain string (used as label) or a rich array:

Column option shape

Each key in matrixColumns() is a selectable column id (stored in state).

State format

Radio mode — one column key per row (or omitted if empty):
Checkbox mode — list of column keys per row:
  • Default state: []
  • On form save, unknown rows, invalid column keys and malformed selections fail validation. Empty rows are omitted from the saved value after successful validation.
  • Use Eloquent cast 'field' => 'array' or 'field' => 'json'

Validation

Built-in (per row)

Translation keys (resources/lang/en/default.php):

Custom cross-row rules

Use standard Filament ->rule() for business logic across rows:

Configuration API

mode('radio'|'checkbox')

rows(array|Closure $rows)

Row definitions — see Row option shape. Accepts Closure for dynamic rows.

matrixColumns(array|Closure $columns)

Column headers — see Column option shape.

columnIcons(array|Closure $icons)

Per-column icon map merged into column metadata:

requiredRows(array|Closure $keys)

Mark rows as required without inline required => true:

disabledRows(array|Closure $keys)

Lock entire rows by key (static, always on):

disabledCells(array|Closure $map)

Lock specific cells. Map shape: rowKey => [columnKey, ...]:
Accepts Closure for server-side dynamic maps (re-evaluated on each render; use with live() for server-driven updates).

disableCellWhen($row, $column, $whenRow, $whenColumns)

Reactive (client-side Alpine) — disable one cell when a trigger row matches column key(s). No live() needed.
Invalid selections in newly disabled cells are removed automatically.

disableRowWhen($row, $whenRow, $whenColumns)

Reactive — disable an entire row when trigger row matches:

size('sm'|'md'|'lg')

Control scale for row labels, column headers, and radio/checkbox indicators. Default: md.

color('primary'|'secondary'|'success'|'warning'|'danger'|null)

Filament accent for selected radio/checkbox indicators. Default: primary.

Inherited Filament field API

Also supports standard Inherited Filament field API:

Public helper methods

FlexField schema config

disable_cell_when / disable_row_when rule array:

CSS classes

Implementation notes

  • Radio/checkbox indicators reuse Flex Radiolist / Flex Checklist animation tokens (fff-choice-cards-indicator-pop).
  • All clicks are handled on fff-matrix-choice__cell; inner inputs use pointer-events-none to prevent double-toggle.
  • Conditional rules update locally when the selection changes. Newly disabled selections are cleared; the server independently rejects submitted selections that violate those rules.

Playground

Slug: matrix-choice /admin/flex-fields/matrix-choice — see Playground.

Keyboard and text direction

Tab enters the grid and the next Tab leaves it. Arrow keys move spatially between enabled cells; Home and End reach the enabled endpoints of the current row. Space or Enter selects the focused cell. Holding the key does not toggle it repeatedly. Horizontal arrows follow RTL direction.
Radio row values must be string or integer column keys. Checkbox row values must be lists of unique string or integer column keys. Booleans, nested objects, associative lists, unknown rows and selections in disabled cells fail server validation. Per-row maximums also prevent further selection immediately in the UI. Reactive row, column and cell configuration is reevaluated when the form renders.
Last modified on October 16, 2026