Source Code

<div class="apm-wrap">
  <div class="apm-head">
    <h2>Role Permissions</h2>
    <p>Choose what each role can do across your workspace.</p>
  </div>
  <div class="apm-scroll">
    <table class="apm-table" id="apmTable">
      <thead>
        <tr id="apmHeadRow">
          <th class="apm-perm-col">Permission</th>
        </tr>
      </thead>
      <tbody id="apmBody"></tbody>
    </table>
  </div>
</div>

Account Permission Matrix Table — Free HTML CSS JS Snippet

Account Permission Matrix Table · Tables · Plain HTML, CSS & JS · Live preview

What's included

Features

Permissions as rows, roles as columns, with a checkbox at every intersection
Per-role "select all" checkbox in each column header that toggles every permission for that role at once
Select-all checkbox state automatically re-syncs after individual cell edits
Owner column is visually and functionally locked — always fully checked and disabled, with an "Always full" badge
Single plain JS state object as the source of truth for every checkbox
Horizontally scrollable table container so the matrix stays usable on narrow viewports
Custom-styled checkboxes (checked, unchecked, and disabled-checked states) built without any UI library
Row hover highlighting to help scan a specific permission across all roles

About this UI Snippet

Account Permission Matrix Table — Roles vs Permissions Grid with Locked Columns

Screenshot of the Account Permission Matrix Table snippet rendered live

Permission matrices are the standard way admin panels let a team owner see and edit what each role in the system can do, at a glance, without navigating into five separate role-edit screens. This snippet renders permissions as rows and roles as columns, with a checkbox at every intersection, so scanning either a row or a column immediately answers "who can do this?" or "what can this role do?"

A plain JS state object drives the whole grid

A single state object keyed by role, then by permission key, holds every checkbox's boolean value. renderBody() reads from it to build every <tr> and <td>, and clicking any checkbox mutates state directly before the per-column select-all indicator is recalculated — there's no hidden duplicate copy of the grid's truth.

Per-column "select all" checkboxes

Each role's header cell carries its own checkbox that, when toggled, sets every permission for that role at once via a loop over PERMISSIONS. refreshSelectAll() keeps that same checkbox's checked state in sync afterward by checking whether every permission for the role is now true — so it accurately reflects "all granted" without ever getting stuck showing stale state after an individual cell changes.

A locked, always-true Owner column

The Owner role is treated specially: isLockedCell() returns true only for the owner key, and every checkbox rendered for that column — including its select-all header checkbox — gets the disabled attribute plus a small "Always full" badge. The change handler also short-circuits and ignores any event on a locked role, so Owner's permissions can never be edited even if a disabled checkbox were somehow triggered programmatically.

Step by step

How to Use

  1. 1
    Load the snippetClick "Account Permission Matrix Table" in the sidebar to load its HTML, CSS, and JS into the editor panels. The preview updates instantly.
  2. 2
    Edit the codeModify any panel — HTML, CSS, or JS. The preview refreshes as you type. Use Reset in each panel header to restore the original.
  3. 3
    Preview on devicesClick the Mobile (375px), Tablet (768px), or Desktop buttons in the preview header to check responsiveness.
  4. 4
    Export in your formatClick "HTML" to download a standalone file, "JSX" for a React component, "Tailwind" for a React + Tailwind CSS component, "Tailwind HTML" for a standalone HTML file with Tailwind CDN, "Vue" for a Vue 3 SFC with <template>/<script setup>/<style scoped>, or "Angular" for a standalone Angular .component.ts file. "Copy all" copies the full code to clipboard.
  5. 5
    Save your versionClick "Save as", type a name, and press Enter. Your snippet saves to IndexedDB and appears in the Saved tab.

Real-world uses

Common Use Cases

Team and workspace admin panels
Let a workspace owner configure exactly what each role can view, edit, or delete.
Role-based access control (RBAC) settings screens
A compact way to expose and edit an RBAC configuration without a form per role.
SaaS billing and enterprise plan management
Show which permissions are bundled or locked at different plan tiers.
Teaching grid-based state management in vanilla JS
A clear example of driving a two-dimensional UI from one nested state object.

Got questions?

Frequently Asked Questions

Every checkbox rendered for the owner role — including its column select-all checkbox — gets the disabled attribute, and the change handler explicitly ignores any event coming from a locked role, so its permissions can never be toggled off.

After any individual permission checkbox changes, refreshSelectAll() recomputes whether every permission for that role is currently true and sets the header checkbox accordingly, so it never shows a stale "all selected" state.

Yes — add entries to the ROLES and PERMISSIONS arrays and a matching key in the state object for any new role; renderHead() and renderBody() rebuild the whole grid from those arrays automatically.