Lightweight access control utilities for React applications. The package provides a small context provider, a declarative Can component, focused hooks, pure utility functions, and an optional React Router entrypoint for page protection.
GitHub: https://github.com/rqdhw3ns
- Tiny runtime footprint
- React 18+ compatible
- Works with Vite, Next.js, CRA, and TypeScript React apps
- Roles and permissions support
- Declarative and programmatic access checks
- Optional React Router v6 route protection
- Shared access context across both the main package and router entrypoint
- ESM, CJS, and TypeScript declaration output
Install the core package:
npm install @rqdhw3n/react-access-controlIf you want route protection, install React Router too:
npm install @rqdhw3n/react-access-control react-router-domyarn add @rqdhw3n/react-access-control react-router-dompnpm add @rqdhw3n/react-access-control react-router-domimport {
AccessProvider,
Can,
useCanPermission
} from '@rqdhw3n/react-access-control';
function DeleteButton() {
const canDeleteUsers = useCanPermission('users.delete');
if (!canDeleteUsers) {
return null;
}
return <button type="button">Delete user</button>;
}
export function App() {
return (
<AccessProvider
roles={['admin']}
permissions={['users.create', 'users.delete']}
>
<Can role="admin">
<section>Admin panel</section>
</Can>
<DeleteButton />
</AccessProvider>
);
}Wrap your app with AccessProvider to make the current user's roles and permissions available throughout the tree.
import { AccessProvider } from '@rqdhw3n/react-access-control';
<AccessProvider
roles={['admin']}
permissions={['users.create', 'users.delete']}
>
<App />
</AccessProvider>;Render children only when the access requirement is satisfied.
import { Can } from '@rqdhw3n/react-access-control';
<Can role="admin">
<AdminPanel />
</Can>;<Can permission="users.delete" fallback={<NoAccess />}>
<DeleteButton />
</Can>;<Can permissions={['users.create', 'users.update']} requireAll>
<UserActions />
</Can>;You can combine role and permission requirements. When requireAll is false, matching any supplied role or permission is enough. When requireAll is true, every supplied role and permission requirement must match.
ProtectedRoute is available from a separate router entrypoint so apps that do not use React Router do not need the dependency.
It uses the same AccessProvider context as the main package, so one provider can drive both component-level and route-level access checks.
import { ProtectedRoute } from '@rqdhw3n/react-access-control/router';ProtectedRoute must be rendered inside AccessProvider.
import { AccessProvider } from '@rqdhw3n/react-access-control';
import { ProtectedRoute } from '@rqdhw3n/react-access-control/router';
<AccessProvider
roles={['admin']}
permissions={['users.view', 'users.delete']}
>
<ProtectedRoute permission="users.delete" redirectTo="/">
<DeleteUsersPage />
</ProtectedRoute>
</AccessProvider>;import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { AccessProvider } from '@rqdhw3n/react-access-control';
import { ProtectedRoute } from '@rqdhw3n/react-access-control/router';
function App() {
return (
<AccessProvider
roles={['admin']}
permissions={['dashboard.view', 'users.view', 'users.delete']}
>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute permission="dashboard.view" redirectTo="/">
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<ProtectedRoute role="admin" redirectTo="/">
<AdminPage />
</ProtectedRoute>
}
/>
<Route
path="/users/delete"
element={
<ProtectedRoute permission="users.delete" redirectTo="/">
<DeleteUsersPage />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
</AccessProvider>
);
}<ProtectedRoute permission="users.delete" redirectTo="/">
<DeleteUsersPage />
</ProtectedRoute><ProtectedRoute role="admin" fallback={<NoAccessPage />}>
<AdminPage />
</ProtectedRoute>If access is denied, ProtectedRoute behaves like this:
redirectToprovided: renders React Router's<Navigate />fallbackprovided withoutredirectTo: renders the fallback- neither provided: returns
null
import { useAccess } from '@rqdhw3n/react-access-control';
function Summary() {
const { roles, permissions, canAccess } = useAccess();
return (
<pre>
{JSON.stringify(
{
roles,
permissions,
canManageUsers: canAccess({ permission: 'users.manage' })
},
null,
2
)}
</pre>
);
}import { useCanRole } from '@rqdhw3n/react-access-control';
function AdminBadge() {
const isAdmin = useCanRole('admin');
return isAdmin ? <span>Admin</span> : null;
}import { useCanPermission } from '@rqdhw3n/react-access-control';
function CreateUserButton() {
const canCreateUser = useCanPermission('users.create');
return canCreateUser ? <button>Create user</button> : null;
}import { useCanAccess } from '@rqdhw3n/react-access-control';
function UserActions() {
const canManageUsers = useCanAccess({
roles: ['admin', 'manager'],
permissions: ['users.create', 'users.update'],
requireAll: false
});
return canManageUsers ? <div>User actions</div> : null;
}All utility functions are pure and can be used outside React.
import {
hasRole,
hasPermission,
canAccess
} from '@rqdhw3n/react-access-control';
hasRole(['admin', 'editor'], ['admin']);
hasPermission(['users.create'], ['users.create', 'users.update'], false);
canAccess({
userRoles: ['manager'],
userPermissions: ['reports.view'],
roles: ['admin', 'manager'],
permission: 'reports.view'
});| Prop | Type | Required | Description |
|---|---|---|---|
roles |
string[] |
No | Current user roles |
permissions |
string[] |
No | Current user permissions |
children |
ReactNode |
Yes | React tree that receives access context |
| Prop | Type | Required | Description |
|---|---|---|---|
role |
string |
No | Single required role |
roles |
string[] |
No | Multiple required roles |
permission |
string |
No | Single required permission |
permissions |
string[] |
No | Multiple required permissions |
requireAll |
boolean |
No | Require all supplied checks to pass |
fallback |
ReactNode |
No | Rendered when access is denied |
children |
ReactNode |
Yes | Rendered when access is allowed |
| Prop | Type | Required | Description |
|---|---|---|---|
role |
string |
No | Single required role |
roles |
string[] |
No | Multiple required roles |
permission |
string |
No | Single required permission |
permissions |
string[] |
No | Multiple required permissions |
requireAll |
boolean |
No | Require all supplied checks to pass |
redirectTo |
string |
No | Redirect path when access is denied |
fallback |
ReactNode |
No | Rendered when access is denied and no redirect is used |
children |
ReactNode |
Yes | Rendered when access is allowed |
The package ships with full type declarations and exports the main public types:
import type {
AccessProviderProps,
CanProps,
AccessContextValue,
AccessCheckOptions,
ProtectedRouteProps
} from '@rqdhw3n/react-access-control';This package only controls client-side rendering and navigation. Your backend must still enforce roles, permissions, and authorization rules for every protected action and route.
A minimal Vite example app is included in example/ for local development and manual testing.
MIT