React Permission Matrix
Every enterprise app needs a role editor, and every team rebuilds it — usually without inheritance, which is where the real complexity lives. This grid groups permissions by resource, resolves grants down an inheritance chain, and shows an inherited allow distinctly from one you set explicitly.
| Permission | Viewer | OperatorViewer | AdminOperator | OwnerAdminlocked |
|---|---|---|---|---|
| Dashboards | ||||
| View dashboards | ||||
| Edit dashboards | ||||
| Alarms | ||||
| View alarms | ||||
| Acknowledge alarms | ||||
| Shelve alarmsSuppress nuisance alarms | ||||
| Control | ||||
| Change setpoints | ||||
| Start / stop equipment | ||||
| Administration | ||||
| Manage users | ||||
| Manage billing | ||||
Installation
Props
| Prop | Type | Description |
|---|---|---|
| roles | RoleColumn[] | { key, label, inheritsFrom?, locked? } |
| permissions | PermissionRow[] | { key, label, group, description? } |
| defaultGrants | Record<string, Record<string, Grant>> | grants[role][perm] = allow/deny/unset |
| onChange | (grants) => void | Fires on every cell change |
Three states, not two
The mistake most permission UIs make is treating a cell as a checkbox — on or off. That cannot express "inherit from the parent role", so inheritance either doesn't exist or is invisible. Here a cell is one of three states:
- Allow / Deny — set explicitly on this role, and an explicit deny beats an inherited allow.
- Unset — inherit. The component walks
inheritsFromuntil it finds an explicit grant, and renders an inherited allow as a lighter check so you can see it came from a parent, not from here.
Clicking cycles allow → deny → inherit. resolve() walks the chain with a cycle guard, so a misconfigured inheritsFrom loop can't hang the render. Locked roles (a built-in Owner) are read-only. This is an editor — enforce the resolved grants server-side; never trust the browser's computed matrix as your access-control decision.
New components every week
Get the week's new Kelvin UI components and templates in one short email. No spam, unsubscribe anytime.