npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-permissions-solution

v1.0.1

Published

Lightweight, declarative role-based and permission-based UI rendering for React. TypeScript-first, zero dependencies.

Readme

react-permissions-solution

Lightweight, declarative role-based and permission-based UI rendering for React.
TypeScript-first. Zero dependencies. Full wildcard support. Flexible by design.

npm version bundle size license


Why react-permissions-solution?

Every app with users has roles. Every app with roles ends up with code like this scattered everywhere:

{user.role === 'admin' && <DeleteButton />}
{user.role === 'admin' || user.role === 'reviewer' ? <EditButton /> : null}
{permissions.includes('read:posts') && <PostList />}

This approach breaks down fast — logic is duplicated, hard to trace, and impossible to change safely.

react-permissions-solution gives you a single source of truth for access control, a declarative API to use it, and the flexibility to plug in any backend.


Installation

npm install react-permissions-solution
# or
yarn add react-permissions-solution
# or
pnpm add react-permissions-solution

Peer dependencies: React 17+


Quick Start

1. Define your permissions

// permissions.config.ts
export const ROLES = {
  admin:    ['*'],
  reviewer: ['read:posts', 'edit:posts', 'approve:posts', 'read:users'],
  student:  ['read:posts', 'read:courses', 'submit:assignments'],
  user:     ['read:posts', 'create:comments'],
}

2. Wrap your app

// App.tsx
import { PermissionsProvider } from 'react-permissions-solution'
import { ROLES } from './permissions.config'

function App() {
  const { user } = useAuth()

  return (
    <PermissionsProvider
      role={user.role}
      permissions={ROLES[user.role] ?? []}
    >
      <Router />
    </PermissionsProvider>
  )
}

3. Use it anywhere

import { Can, usePermissions } from 'react-permissions-solution'

// Component-based
<Can do="delete" on="posts">
  <DeleteButton />
</Can>

// Hook-based
const { can, is } = usePermissions()
if (can('delete', 'posts')) { ... }
if (is('admin')) { ... }

Core Concepts

Permission format

Permissions are plain strings. The recommended format is action:resource:

'read:posts'
'edit:users'
'approve:comments'

But you can use any format that fits your system:

'admin:access'
'feature_x_enabled'
'can_export_pdf'

Wildcards

| Permission | Matches | |---|---| | * | Everything — full access | | read:* | Any read action on any resource | | *:posts | Any action on posts | | read:posts | Exactly read on posts |


API Reference

<PermissionsProvider>

Wraps your app (or any subtree). Must be a parent of any <Can> or usePermissions() usage.

<PermissionsProvider
  permissions={['read:posts', 'edit:posts']}
  role="reviewer"
  extraPermissions={['approve:posts']}   // merged on top of permissions
>
  {children}
</PermissionsProvider>

| Prop | Type | Required | Description | |---|---|---|---| | permissions | string[] | ✅ | The user's permissions | | role | string | ❌ | The user's role | | extraPermissions | string[] | ❌ | Additional permissions merged on top (e.g. user-level overrides) |


<Can>

Declarative component for conditional rendering.

<Can
  do="edit"
  on="posts"
  mode="all"
  role="reviewer"
  fallback={<p>Access denied</p>}
>
  <EditButton />
</Can>

| Prop | Type | Default | Description | |---|---|---|---| | do | string \| string[] | required | Action(s) to check | | on | string | undefined | Resource to check against | | mode | 'all' \| 'any' | 'all' | When do is array: must have ALL or ANY | | fallback | ReactNode | null | What to render when check fails | | role | string | undefined | Also requires this exact role | | roles | string[] | undefined | Also requires ANY of these roles | | not | boolean | false | Invert — show when permission is NOT granted |

Examples

// Basic
<Can do="read" on="posts">
  <PostList />
</Can>

// With fallback
<Can do="edit" on="posts" fallback={<ReadOnlyView />}>
  <EditForm />
</Can>

// Multiple permissions — all required (default)
<Can do={['edit', 'publish']} on="posts">
  <PublishButton />
</Can>

// Multiple permissions — any is enough
<Can do={['edit', 'approve']} on="posts" mode="any">
  <ActionPanel />
</Can>

// Require a specific role too
<Can do="edit" on="posts" role="reviewer">
  <ReviewerEditPanel />
</Can>

// Invert — show to users WITHOUT this permission
<Can do="admin:access" not>
  <UpgradeBanner />
</Can>

// Role-only check (no permission needed)
<Can do="*" role="admin">
  <AdminBadge />
</Can>

usePermissions()

Hook that gives you the full permissions API.

const {
  can,         // (action, resource?) => boolean
  canAll,      // (actions[], resource?) => boolean
  canAny,      // (actions[], resource?) => boolean
  cannot,      // (action, resource?) => boolean  (negation of can)
  is,          // (role) => boolean
  isAny,       // (roles[]) => boolean
  permissions, // string[] — the full list
  role,        // string | undefined — the current role
} = usePermissions()

Examples

function PostActions({ post }) {
  const { can, is } = usePermissions()

  // Use in conditionals
  if (!can('read', 'posts')) return <Navigate to="/unauthorized" />

  return (
    <div>
      <ViewButton />
      {can('edit', 'posts') && <EditButton />}
      {can('delete', 'posts') && <DeleteButton />}
      {is('admin') && <AdminControls />}
    </div>
  )
}
// In event handlers
function handleDelete() {
  if (!can('delete', 'posts')) {
    toast.error('You do not have permission to delete posts')
    return
  }
  deletePost(post.id)
}
// canAll — require multiple permissions
const canPublish = canAll(['edit', 'publish'], 'posts')

// canAny — require at least one
const canModerate = canAny(['edit', 'delete', 'approve'], 'posts')

withPermission(options)(Component)

HOC for protecting entire pages or sections.

import { withPermission } from 'react-permissions-solution'

// Protect by permission
const AdminPage = withPermission({
  permission: 'admin:access',
  fallback: <Navigate to="/unauthorized" />,
})(AdminDashboard)

// Protect by role
const ReviewerPage = withPermission({
  role: 'reviewer',
  fallback: <p>Reviewers only</p>,
})(ReviewDashboard)

// Protect by permission AND role
const SpecialPage = withPermission({
  permission: 'edit:posts',
  role: 'reviewer',
})(EditPage)

// Protect by any of multiple permissions (mode: 'any')
const ModPage = withPermission({
  permission: ['edit:posts', 'delete:posts'],
  mode: 'any',
})(ModerationPanel)

| Option | Type | Description | |---|---|---| | permission | string \| string[] | Required permission(s) | | resource | string | Resource to check against | | mode | 'all' \| 'any' | Array match mode (default: 'all') | | role | string | Required role | | roles | string[] | Any of these roles | | fallback | ReactNode | Rendered when denied (default: null) |


Utility Functions

All core functions are exported — use them to build your own helpers.

import {
  hasPermission,
  hasAllPermissions,
  hasAnyPermission,
  matchRole,
  matchAnyRole,
  mergePermissions,
  buildRolePermissions,
  buildPermissionString,
} from 'react-permissions-solution'

mergePermissions(...permissionSets)

Merges multiple permission arrays. Deduplicates. Collapses to ['*'] if wildcard is present.

mergePermissions(
  ROLES['reviewer'],
  user.extraPermissions,
)

buildRolePermissions(config)

Normalizes a role config object (lowercases keys, deduplicates permissions).

const roles = buildRolePermissions({
  ADMIN:    ['*'],
  Reviewer: ['read:posts', 'edit:posts', 'read:posts'],  // deduped
})
// → { admin: ['*'], reviewer: ['read:posts', 'edit:posts'] }

Real-World Patterns

Pattern 1 — Hardcoded roles with groups

Best for: small-medium apps with fixed roles.

// permissions.config.ts
const PERMISSIONS = {
  posts: {
    all:      ['read:posts', 'create:posts', 'edit:posts', 'delete:posts'],
    readOnly: ['read:posts'],
    editor:   ['read:posts', 'create:posts', 'edit:posts'],
  },
  users: {
    all:      ['read:users', 'create:users', 'edit:users', 'delete:users'],
    readOnly: ['read:users'],
  },
}

export const ROLES = {
  admin:    ['*'],
  reviewer: [...PERMISSIONS.posts.editor, 'approve:posts', ...PERMISSIONS.users.readOnly],
  student:  ['read:posts', 'read:courses', 'submit:assignments'],
  user:     ['read:posts', 'create:comments'],
}

Pattern 2 — Backend-driven permissions

Best for: apps where admins manage roles/permissions at runtime.

function App() {
  const { user } = useAuth()

  return (
    <PermissionsProvider
      role={user.role}
      permissions={user.permissions}   // straight from your API response
    >
      <Router />
    </PermissionsProvider>
  )
}

Your API just needs to return the permissions array:

{
  "role": "reviewer",
  "permissions": ["read:posts", "edit:posts", "approve:posts"]
}

Pattern 3 — User-level overrides on top of roles

Best for: enterprise apps where individual users can have extra permissions.

<PermissionsProvider
  role={user.role}
  permissions={ROLES[user.role]}
  extraPermissions={user.extraPermissions}   // merged automatically
>

Pattern 4 — Protecting routes

// ProtectedRoute.tsx
import { withPermission } from 'react-permissions'
import { Navigate } from 'react-router-dom'

export function requirePermission(permission: string) {
  return function protect<P extends object>(Component: ComponentType<P>) {
    return withPermission({
      permission,
      fallback: <Navigate to="/unauthorized" replace />,
    })(Component)
  }
}

// Usage
const AdminPage = requirePermission('admin:access')(AdminDashboard)
const ReviewPage = requirePermission('approve:posts')(ReviewDashboard)

Pattern 5 — Outside React (e.g. API calls)

import { hasPermission } from 'react-permissions-solution'

// in your API service layer
async function deletePost(id: string, userPermissions: string[]) {
  if (!hasPermission(userPermissions, 'delete', 'posts')) {
    throw new Error('Forbidden')
  }
  return api.delete(`/posts/${id}`)
}

Edge Cases Handled

| Case | Behavior | |---|---| | permissions is empty [] | All can() calls return false | | permissions contains '*' | All can() calls return true | | do is empty string | Returns false gracefully | | role is undefined | is() always returns false | | Used outside <PermissionsProvider> | Throws a clear, descriptive error | | Permission strings with different casing | Normalized — case-insensitive matching | | extraPermissions contains '*' | Full access granted, mergePermissions collapses to ['*'] | | do={[]} (empty array) with mode all | Returns true (vacuously true — no requirements) | | do={[]} with mode any | Returns false (nothing to match) |


TypeScript

Everything is fully typed. Key types you can import:

import type {
  Permission,           // string
  Role,                 // string
  PermissionsContextValue,
  PermissionsProviderProps,
  CanProps,
  WithPermissionOptions,
} from 'react-permissions-solution'

Bundle Size

  • Zero runtime dependencies
  • Tree-shakeable (pure ESM + CJS)
  • ~2KB minzipped

License

MIT