Add this component

npx zentauri-ui add permission

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Utilities

Permission system for access control

Use the permission system to declaratively protect components, routes, buttons, pages, menu items, and actions. Instead of manually checking roles or permissions throughout your application, wrap content with <Can>, check permissions with usePermission, and guard routes with RouteGuard.

Provider<PermissionProvider>
Component<Can permission="users.read">
HookusePermission("users.delete")
Guard<RouteGuard permission="admin">

Interactive playground

Toggle permissions and roles below to see how Can, Cannot, Role, and the hooks react in real time.

Permissions

Roles

Current permissions: users.read, users.delete, billing.view

Current roles: admin


Declarative components:

✅ <Can> renders — you can read users
✅ <Can> renders — you can delete users
✅ <Role> renders — you are an admin

Hooks:

usePermission("users.delete") true

useRole("admin") true

Provider setup

Wrap your application with PermissionProvider to enable permission and role checking throughout the component tree.

import { PermissionProvider } from "@zentauri-ui/zentauri-components/permission";

function App() {
  return (
    <PermissionProvider
      roles={["admin"]}
      permissions={[
        "users.read",
        "users.create",
        "users.delete",
        "billing.view",
      ]}
    >
      <YourApp />
    </PermissionProvider>
  );
}

Declarative components

Use Can, Cannot, and Role to conditionally render UI elements.

import { Can, Cannot, Role } from "@zentauri-ui/zentauri-components/permission";

<Can permission="users.delete">
  <button>Delete User</button>
</Can>

<Can permissions={["users.create", "users.update"]} mode="all">
  <button>Edit</button>
</Can>

<Cannot permission="billing.edit">
  <Alert>You cannot edit billing.</Alert>
</Cannot>

<Role name="admin">
  <AdminPanel />
</Role>

Hooks

Programmatic permission and role checking with hooks.

import { usePermission, useRole, useCan } from "@zentauri-ui/zentauri-components/permission";

function DeleteButton() {
  const canDelete = usePermission("users.delete");
  const isAdmin = useRole("admin");

  const { allowed, missingPermissions } = useCan({
    permissions: ["users.delete", "users.create"],
    mode: "any",
  });

  return canDelete ? <button>Delete</button> : null;
}

Route guard

Protect entire route segments with RouteGuard.

import { RouteGuard } from "@zentauri-ui/zentauri-components/permission";

<RouteGuard
  permission="users.read"
  fallback={<div>Access denied</div>}
  redirectTo="/403"
>
  <UsersPage />
</RouteGuard>

Async permissions

Load permissions from an API asynchronously.

import { PermissionProvider, PermissionBoundary } from "@zentauri-ui/zentauri-components/permission";

function App() {
  return (
    <PermissionProvider
      loadPermissions={async () => {
        const res = await fetch("/api/permissions");
        return res.json();
      }}
      loadRoles={async () => {
        const res = await fetch("/api/roles");
        return res.json();
      }}
    >
      <PermissionBoundary
        loading={<Spinner />}
        fallback={<NoAccess />}
      >
        <Dashboard />
      </PermissionBoundary>
    </PermissionProvider>
  );
}

What it does

The permission system provides a complete authorization layer for React applications. At its core is the PermissionProvider context that stores the current user's permissions and roles. From there, you can use declarative components like Can, Cannot, and Role to conditionally render UI, hooks like usePermission and useCan for programmatic checks, and RouteGuard for route-level protection.

The system supports role-based access control (RBAC), permission-based access control (PBAC), wildcards, async permission loading, and both "all" and "any" matching modes.

Common use cases

  • Protect UI elements like buttons, links, and menu items based on user permissions.
  • Guard entire route segments with RouteGuard and redirect unauthorized users.
  • Show or hide page sections based on roles using the Role component.
  • Provide loading and fallback states while permissions are being fetched asynchronously.
  • Build admin panels where only users with specific roles can access management features.

API reference

Import everything from @zentauri-ui/zentauri-components/permission.

Components: PermissionProvider (context provider), Can (render if authorized), Cannot (inverse of Can), Role (render if role matches), RouteGuard (route-level protection with redirect), PermissionBoundary (empty-permission fallback boundary).

Hooks: usePermission (check single permission), usePermissions (get all permissions), usePermissionsRefresh (get refresh function), useRole (check role), useRoles (get all roles), useCan (detailed multi-permission check with missing list), usePermissionContext (raw context access).

Utilities: hasPermission, hasAnyPermission, hasAllPermissions, hasRole, mergePermissions, getMissingPermissions, matchWildcard, hasWildcard.

Next.js integration notes

Keep PermissionProvider in a client component wrapper around your layout or app. Use RouteGuard for route-level protection. Components like Can and Cannot run in client components and respect the provider context.

FAQ

Does the Permission System work with Next.js App Router?

Yes. Wrap your layout or app with PermissionProvider inside a client component boundary. RouteGuard performs client-side gating and redirect — it cannot protect server components or backend resources. For protected routes and data, combine RouteGuard with server-side authorization checks in your API routes or server components.

How do I load permissions from an API?

Use the loadPermissions prop on PermissionProvider with an async function. The provider will show the fallback UI while permissions are loading.

Can I use wildcards in permissions?

Yes. The permission system supports wildcards like users.*, billing.*, and *. This allows you to define broad permission sets and check granular permissions.

What's the difference between Can and RouteGuard?

Can conditionally renders its children based on permissions or roles. RouteGuard additionally supports a redirectTo prop that navigates away when unauthorized, making it suitable for route-level protection.