Skip to main content
Copy for LLM

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.

Editable role × capability grants
Role Billing Members Projects Settings
Owner
Admin
Member
Guest
Allowed Denied Inherited

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.

Retention cohort heat grid
Cohort Week 0 Week 1 Week 2 Week 3
January
100%
68%
54%
41%
February
100%
72%
60%
48%
March
100%
75%
63%
52%
0% 100%

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.

Compliance controls × frameworks
Control SOC 2 ISO 27001 HIPAA
MFA enforced met met met
Encryption at rest met met at-risk
Tested backups at-risk failing failing

Preferences Grid

cell-type="toggle" with editable gives a type × channel on/off grid — e.g. a notification-preferences matrix.

Notification preferences (type × channel)
Notify me about Email Push SMS
Mentions
Comments
Weekly digest
On Off

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

On paper the column and row labels wrap between words, so a matrix needs less of the sheet's width than of the screen's, and its cells keep their size. A sheet does not scroll, so a matrix still wider than the sheet prints the columns that fit. A landscape sheet holds more of them: give the page that prints the matrix @page { size: landscape; }.

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
emptyText string Nothing here yet Sentence shown when rows or columns is empty. The empty slot replaces it entirely.
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|null null Accessible name for the matrix table, and the switch that makes the scroll wrapper a screen-reader landmark. The table itself is always named — it falls back to 'Status matrix'; the wrapper's role="region" waits for your name. See Accessibility
name string|null null Hidden-input name for form submission. Without an id the matrix's id derives from it; a second matrix with the same name on the page gets -2
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 matrix is a <table> with scope="col" / scope="row" headers, and it is always named — ariaLabel when you set one, "Status matrix" otherwise. A table with no accessible name is worse than a duplicated region, so this fallback stays.
  • role="grid" is added only for cell-type="tristate" and cell-type="toggle". The grid role is a promise of a keyboard model — one tab stop into the widget, arrows from there — and only those two cell types render something focusable to hold it. A status or heat matrix is a data table with nothing to operate, so it stays a plain table and your reader keeps their own table-navigation mode, which announces the row and column header as they move. A grid role there would replace that with an application-widget model and hand them no way in.
  • The scroll wrapper carries tabindex="0" and a focus ring in every configuration, so it is reachable and operable by keyboard (WCAG 2.1.1).
  • The wrapper becomes a screen-reader landmark only when you set ariaLabel. It then also gets role="region" under that name. Without one it stays a plain focusable scroller — role="region" plus a name is a landmark, and three matrices on a page all falling back to "Status matrix" would be three landmarks announcing the same thing, which axe reports as landmark-unique.
  • 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 expose role="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.
  • An editable tristate matrix with its legend says the number of unsaved changes through a role="status" live region each time it changes, from the first change on. The region is in the page before that change, because one that appears together with its text is announced unreliably; the count in the legend is the same text for the eye.
  • An aria-labelledby of yours names the matrix and its scroll region, as aria-label does.

Keyboard Interaction

Key Action
Tab Enter / leave the grid (one stop), then reach the scroll region
Arrow keys Move focus between interactive cells
Home / End First / last cell of the current row
Ctrl+Home / Ctrl+End First / last cell of the whole grid
Enter / Space Cycle a tristate cell or flip a toggle

This table describes cell-type="tristate" and cell-type="toggle". A status or heat matrix has no focusable cells and no grid role: Tab reaches its scroll region and stops there, and the cells are read with your screen reader's own table navigation.

Navigation works whether or not the matrix is editable — only Enter / Space are gated on it. A read-only tristate or toggle matrix marks its cells aria-disabled="true", so they stay focusable and readable while announcing that there is nothing to activate.

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
Toggle on --color-wk-success
Toggle off (a ring, at 3:1 against the page) --color-wk-border-strong
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,
    ],
],

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store