Status Matrix
One grid engine for every rows × columns status surface: a role-permission
matrix (role × capability → allow / deny / inherit), a heat grid (cohort ×
period → value), a controls matrix (control × framework → met / at-risk /
failing), or a preferences grid (type × channel → on / off). Pick the
cell-type and the matrix renders the right interactive (or read-only) cell —
on sticky row and column headers, inside a keyboard-reachable scroll region.
Every encoding is conveyed in text or shape, never color alone: tristate cells use distinct check / cross / dash glyphs, heat cells always print their value, and status cells render a labeled badge.
Role Permission Matrix
cell-type="tristate" with editable cycles each cell inherit → allow → deny
on click or Enter/Space. Edited cells get a "changed" ring and the legend
counts unsaved changes — ideal for an IAM grant editor.
Heat Grid
cell-type="heat" color-mixes each cell's background from the surface toward
the scale color by its normalized value — and always prints the value, so the
grid stays legible for colorblind readers.
Controls Matrix
cell-type="status" renders each value as an intent badge — common status
words (met, at-risk, failing) map to success / warning / danger
automatically. Perfect for a compliance control × framework grid.
Preferences Grid
cell-type="toggle" with editable gives a type × channel on/off grid — e.g.
a notification-preferences matrix.
Cell Types
cell-type |
Editable | Cell renders | Typical use |
|---|---|---|---|
tristate |
yes | inherit / allow / deny glyph | role-permission matrix |
toggle |
yes | on / off dot | preferences (type × channel) |
status |
no | intent badge | compliance / controls grid |
heat |
no | color-scaled value | retention / metric heatmap |
Data Shapes
rows and columns are each [{key, label}]. cells is a value map keyed by
"rowKey:colKey" — or a nested [rowKey => [colKey => value]] map; both are
accepted.
// 1. Flat key shape
$cells = ['admin:billing' => 'allow', 'admin:users' => 'deny'];
// 2. Nested shape (equivalent)
$cells = ['admin' => ['billing' => 'allow', 'users' => 'deny']];
Status Value → Intent
status cells map these words automatically (case-insensitive); any other value
renders neutral:
| Intent | Words |
|---|---|
| success | met · pass · ok · active · compliant · done · on |
| warning | at-risk · warning · pending · partial · review |
| danger | failing · fail · error · inactive · breach · off |
The Emitted Value (Editable)
When editable, every cell change emits the normalized map two ways — a
bubbling cell-change event and a JSON hidden input — so wire:model and forms
both bridge:
{{-- 1. Bind the JSON bridge to a Livewire property --}}
<x-wirekit::status-matrix
cell-type="tristate"
editable
:rows="$roles"
:columns="$capabilities"
:cells="$grants"
wire:model.live="grantsJson"
/>
// 2. Decode + persist the edited grants
public string $grantsJson = '{}';
public function save(): void
{
$grants = json_decode($this->grantsJson, true) ?: [];
// 3. $grants is ['admin:billing' => 'deny', ...] — persist as you like
foreach ($grants as $key => $value) {
[$role, $capability] = explode(':', $key);
Grant::updateOrCreate(compact('role', 'capability'), ['value' => $value]);
}
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
rows |
array | [] |
Row axis [{key, label}] |
columns |
array | [] |
Column axis [{key, label}] |
cells |
array | [] |
Value map ("row:col" => value or nested) |
cellType |
string | 'status' |
tristate · toggle · status · heat |
editable |
bool | false |
Make tristate / toggle interactive |
cornerLabel |
string | '' |
Top-left header cell (the row-axis name) |
ariaLabel |
string | 'Status matrix' |
Accessible name for the grid + scroll region |
name |
string|null | null |
Hidden-input name for form submission |
heatFrom |
string | var(--color-wk-warning) |
Heat ramp low end (cold) — a color/token reference |
heatTo |
string | var(--color-wk-danger) |
Heat ramp high end (hot) — a color/token reference |
heatMin |
numeric | 0 |
Heat scale lower bound |
heatMax |
numeric | 100 |
Heat scale upper bound |
heatUnit |
string | '' |
Suffix on heat value labels (e.g. %) |
legend |
bool | true |
Render the cell-type legend |
scope |
string|null | null |
Scoped personalization name |
Accessibility
- The grid is a
role="grid"withscope="col"/scope="row"headers; the scroll wrapper is a labeledrole="region"withtabindex="0"and a focus ring, so it is reachable and operable by keyboard (WCAG 2.1.1). - Every cell's state is in text or shape, never color alone: tristate cells
carry the state word in their
aria-label("Admin, Billing: Denied") and use distinct glyphs; toggle cells exposerole="switch"+aria-checked; heat cells always print their value; status cells render a labeled badge. - Interactive cells use a single tab stop (roving focus) into the grid; arrow keys then move between cells.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Enter / leave the grid (one stop), then reach the scroll region |
Arrow keys |
Move focus between interactive cells |
Enter / Space |
Cycle a tristate cell or flip a toggle |
Design Tokens
| Element | Token |
|---|---|
| Sticky header surface | --color-wk-bg-elevated |
| Grid border | --color-wk-border |
| Allow glyph | --color-wk-success |
| Deny glyph | --color-wk-danger |
| Inherit glyph | --color-wk-text-subtle |
| Changed-cell ring | --color-wk-warning |
| Heat ramp (cold → hot) | --color-wk-warning → --color-wk-danger (override via heatFrom / heatTo) |
| Focus ring | --color-wk-ring |
Customization
Override the cell-type default and legend visibility via config/wirekit.php:
'components' => [
'status-matrix' => [
'cell-type' => 'tristate',
'legend' => true,
],
],