ComponentsEditors & drag-and-drop

Permission matrix

Edit what a role, group or plan may do: resources × actions with checkbox cells or scope cells (None / Own / Team / All, shown as increasing fill plus text), inheritance from a parent role, row, column and group bulk toggles, dependencies kept consistent ("Edit needs View"), sticky headers, and a review-before-save diff. PermissionCompare puts roles side by side.

WAI-ARIA grid · Dropdown Menu · Dialog business/permission-matrix.tsx, business/permission-core.tstheme · material · accent · density · font
Arrow keys move. Space toggles, Enter opens scopes, 1–4 pick a scope, Delete resets to inherited.
Resource

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { PermissionMatrix, PermissionCompare, diffPermissions } from "@vamos-ui/react/business";

<PermissionMatrix
  resources={resources}
  actions={[{ id: "view", label: "View", scoped: true }, { id: "delete", label: "Delete", danger: true }]}
  value={value}
  onValueChange={setValue}
  inherited={managerRole}
  inheritedFrom="Manager"
  baseline={saved}
  onSave={(v, changes) => api.roles.update(id, v)}
  dependencies={[{ action: "edit", requires: "view" }]}
/>

Keyboard: arrows move (roving tabindex, mirrored in RTL), Home/End go to the row edges, Ctrl+Home/End to the grid corners. Space toggles, Enter opens the scope menu, 1–4 pick a scope, Delete resets an override. The header row and row headers are part of the grid: their buttons grant or revoke a whole column or row.

Every cell has a full name ("Quotes, Delete: Team"); colour never stands alone. Sticky headers sit on an opaque surface and never blur.

Examples

Scoped cells

Own / Team / All as increasing fill with the name in the cell. Enter opens the scope menu, 1–4 pick directly, Space toggles None ↔ All. Raising Delete raises Edit and View (toast).

Arrow keys move. Space toggles, Enter opens scopes, 1–4 pick a scope, Delete resets to inherited.
Resource

Inherited from a parent role

Inherited cells are striped and locked ("From Manager"); overrides get a ring and "Reset to inherited" in the menu.

Arrow keys move. Space toggles, Enter opens scopes, 1–4 pick a scope, Delete resets to inherited.
Resource

Striped cells are inherited from Manager. Delete or Backspace on an overridden cell resets it.

Compare roles

PermissionCompare: subjects as columns, permissions as rows; rows that differ are tinted (onlyDifferences filters them).

Sales roles compared
PermissionSales rep12 peopleSales manager3 peopleAdmin2 people
Deals · View (Differs)OwnTeamAll
Deals · Edit (Differs)OwnTeamAll
Deals · Delete (Differs)—OwnAll
Quotes · View (Differs)OwnTeamAll
Quotes · Edit (Differs)OwnTeamAll
Quotes · Delete (Differs)——All
Contacts · View (Differs)TeamAllAll
Contacts · Edit (Differs)OwnTeamAll
Contacts · Delete (Differs)——All

Review before save

With baseline, changed cells get a dot and a floating bar shows "3 changes · Review · Discard · Save". The review lists every change as text and flags sensitive grants.

Arrow keys move. Space toggles, Enter opens scopes, 1–4 pick a scope, Delete resets to inherited.
Resource
3 changes

Plan entitlements

Features × plans with boolean cells, relabelled Included / Not included, inside a scroll area with sticky headers.

Arrow keys move. Space toggles, Enter opens scopes, 1–4 pick a scope, Delete resets to inherited.
Feature

API

PermissionMatrix

PropTypeDefault
resources
Rows. group makes collapsible sections; actions limits which cells apply (others show "Not applicable").
{ id; label; group?; description?; actions?: string[] }[]—
actions
Columns. scoped → None/Own/Team/All; danger highlights full grants.
{ id; label; scoped?; danger? }[]—
value / defaultValue / onValueChange
Missing entries are not granted (or inherited).
Record<resource, Record<action, Scope | boolean>>—
scopes
Rename scope levels (default from the dictionary).
{ id: Scope; label: string }[]—
inherited / inheritedFrom
Parent role values shown locked until overridden.
PermissionValue / string—
baseline
Saved value: enables change dots, the save bar and the review dialog.
PermissionValue—
onSave
Save button in the bar and the review dialog.
(value, changes) => Promise<void> | void—
subject
Named in the review dialog.
string"this role"
dependencies
Kept consistent both ways; changes are announced.
{ action: string; requires: string }[]—
onDependencyApplied
Show a toast: "View enabled because Edit requires it".
(message: string) => void—
readOnly
System roles: no toggles, cells stay navigable.
booleanfalse
maxHeight
Scroll area height for sticky headers.
string—
labels
Built-in strings (permissionMatrix namespace).
Partial<PermissionMatrixLabels>—

PermissionCompare

PropTypeDefault
subjects
Roles or plans as columns.
{ id; label; description?; value }[]—
resources / actions / scopes
Rows are resource × action.
as above—
onlyDifferences
Hide rows where every subject agrees.
booleanfalse
highlightDifferences
Tint rows that differ.
booleantrue

PermissionDiff

PropTypeDefault
changes
From diffPermissions(a, b).
PermissionChange[]—
resources / actions
For labels.
as above—

core

PropTypeDefault
diffPermissions(a, b)
{ resource, action, from, to } per changed cell (none / false / missing are equal).
PermissionChange[]—
applyDependencies(value, deps, { changed })
Raises requirements or lowers dependents depending on what changed.
{ value, adjusted[] }—