Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bsperm-card">
    <div class="card-body p-3">
      <h6 class="fw-bold mb-2">Role permissions</h6>
      <div class="table-responsive">
        <table class="table table-sm align-middle mb-0" id="bspermTable">
          <thead>
            <tr>
              <th>Permission</th>
              <th class="text-center">Viewer</th>
              <th class="text-center">Editor</th>
              <th class="text-center">Admin</th>
            </tr>
          </thead>
          <tbody></tbody>
        </table>
      </div>
      <p class="small text-muted mt-2 mb-0">Click a cell to toggle it. Admin always keeps every permission.</p>
    </div>
  </div>
</div>

Bootstrap User Permission Matrix — Free HTML CSS JS Snippet

Bootstrap User Permission Matrix · Dashboards · Plain HTML, CSS & JS · Live preview

What's included

Features

Permission state lives in one plain grid object, indexed identically to the rendered rows and columns
Admin permissions are structurally locked, not just pre-checked and still technically editable
The click handler explicitly rejects locked cells before ever touching the underlying grid state
Toggling any unlocked cell is one symmetric operation with no per-permission or per-role special cases
Adding a new permission or role only requires extending the two source arrays, not new logic

About this UI Snippet

Bootstrap User Permission Matrix — HTML, CSS & JavaScript

Screenshot of the Bootstrap User Permission Matrix snippet rendered live

The permission state lives in one plain grid object keyed by role, each holding a boolean array indexed the same way as PERMISSIONSgrid.editor[2] is unambiguously "does the editor role have the third permission," which is what makes rendering the entire table a matter of mapping over two small arrays rather than hand-writing markup for every one of the fifteen cells.

Admin's row isn't just pre-checked — every cell in grid.admin is genuinely fixed to true and separately marked locked in the render function, and the click handler explicitly bails out on a locked cell before touching grid at all. That distinction matters: a "pre-checked but still editable" admin row would let someone accidentally uncheck "Manage billing" for admins and lock the whole team out of billing, which a real permission matrix should treat as structurally impossible, not just discouraged.

Toggling any unlocked cell is a single, symmetric operation — grid[role][row] = !grid[role][row] — with no special-casing per permission or per role beyond the one admin-lock check, which is what keeps adding a sixth permission or a fourth role a matter of extending the two source arrays rather than writing new toggle logic.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to add a "custom role" column that starts with every permission unchecked and lets an admin build a role from scratch, or to add a small diff summary showing exactly which permissions differ between two selected roles.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 clickable user permission matrix, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js), not custom CSS made to resemble it.

Requirements:
- A table with a row per permission (at least 5) and a column per role (at least 3, e.g. Viewer, Editor, Admin), each cell showing a checkmark or dash indicating whether that role has that permission.
- Store all permission state in a single object keyed by role, holding a boolean array per role indexed the same way as the permission rows, and render the entire table from that one structure.
- Clicking an unlocked cell must toggle that specific role/permission combination immediately, updating only that cell's visual state.
- One role (e.g. Admin) must have every permission structurally locked to always-granted — clicking any of its cells must have no effect at all, both visually (styled as non-interactive) and in the click handler logic itself.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetA 5x3 grid renders with Viewer, Editor, and Admin columns, each cell showing a checkmark or dash.
  2. 2
    Click a checkmark or dash in the Viewer or Editor columnIt toggles immediately between granted and not granted.
  3. 3
    Try clicking any cell in the Admin columnNothing happens — every Admin cell is genuinely locked to always-granted, not just pre-checked.
  4. 4
    Toggle several Editor permissions on and offEach cell updates independently, with no effect on Viewer or Admin.

Real-world uses

Common Use Cases

Admin panels managing team or workspace roles
A direct, editable view of exactly what each role can do, more scannable than a paragraph description per role.
Enterprise and B2B SaaS access control settings
Pairs with bootstrap-role-comparison-table-style summaries for a fuller access-management section.
DEV
Internal tools defining custom role-based access
A starting point for any admin UI letting an operator define exactly which permissions a custom role should carry.

Got questions?

Frequently Asked Questions

A pre-checked-but-editable cell can be accidentally unchecked by a stray click, silently removing a permission a role is supposed to always have — locking it structurally (both visually and in the click handler) makes that mistake impossible rather than just unlikely.

Yes — add it to the ROLES array and give it its own boolean array in grid keyed by that role name; render() and the click handler both iterate ROLES generically and need no changes.

Add its label to PERMISSIONS and a corresponding boolean to every role's array in grid, at the same index — the table automatically grows by one row.

Yes. Keep the grid object in component state, and toggle a cell by producing a new grid object with that one role/row value flipped (respecting your framework's immutability conventions) rather than mutating it directly.