Skip to content

Level Matrix

Set a level for each row and column pair, with pending changes and bulk editing, such as access by role and resource.

import { LevelMatrix, PermissionChange, PermissionResource, PermissionRole } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a level matrix when access is decided per role and per resource: who can read billing, who can deploy, who can manage members. Each cell is an access level (none, read, write, admin) drawn as three segments lit to it, so a column or a row reads at a glance. For one person's settings, a list of switches reads better.

import { LevelMatrix, type PermissionValue } from '@youj/react'

const [value, setValue] = useState<PermissionValue>(saved)

<LevelMatrix
  label="Workspace permissions"
  roles={[
    { id: 'owner', name: 'Owner', locked: true },
    { id: 'editor', name: 'Editor', detail: '8 members' },
  ]}
  resources={[
    { id: 'docs', name: 'Docs' },
    { id: 'billing', name: 'Billing' },
  ]}
  value={value}
  baseline={saved}
  onValueChange={setValue}
/>

A value maps a role's id to its levels by resource id; a missing cell is none. A locked role (an owner) can't change and sits out of bulk selection.

Reference

Propsroles, resources, value… (7)

Props

LevelMatrix props
PropType
rolesRequiredreadonly PermissionRole[]–
resourcesRequiredreadonly PermissionResource[]–
valuePermissionValue–
defaultValuePermissionValue

default {}

{}
onValueChange(value: PermissionValue) => void–
baselinePermissionValue

The saved levels: cells that differ from it show their pending gain or loss. Default: the value it opened with.

–
labelstring

Accessible name of the grid. Default: the labels' "Permissions".

–
Other props pass through: extends ComponentProps<'div'>.
MotionA cell changes, A bulk set, the bulk bar · reduced motion

Motion

Motion tokens
MomentTokens
A cell changesits segments light or empty one after another
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
A bulk setthe same, as a wave across the grid from the top-start corner
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
The bulk baropens and closes with the selection
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Levels change in place.

Reduced motion

Levels change in place, without the segment stagger or the wave.

AccessibilityKeyboard, ARIA, announcements

Accessibility

  • It's an ARIA grid, named by label (or the labels' "Permissions") and multiselectable, with column and row headers and a keyboard hint as its description.
  • One tab stop, then the arrow keys move between cells, the header boxes included; Home and End go to a row's ends, and Ctrl+Home and Ctrl+End to the grid's. Space or Enter steps a cell up a level, and Shift steps it down.
  • Every change is announced: "Editor, Billing: Write", or "6 permissions set to Read". A pending cell adds "changed from Read" to its name, selected cells are aria-selected, and a locked role's cells are read-only.
  • Its built-in text comes from the labels, so <YoujProvider labels> translates it.