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.

PermissionViewerOperatorViewerAdminOperatorOwnerAdminlocked
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
Allowed Denied Inherited Not set

Installation

npx shadcn@latest add "https://kelvinui.com/registry/permission-matrix.json"

Props

PropTypeDescription
rolesRoleColumn[]{ key, label, inheritsFrom?, locked? }
permissionsPermissionRow[]{ key, label, group, description? }
defaultGrantsRecord<string, Record<string, Grant>>grants[role][perm] = allow/deny/unset
onChange(grants) => voidFires 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 inheritsFrom until 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.