# Users Admin Page

> A React users admin page: team members and invitations with seats, roles with a permissions matrix and a save bar, and the audit log.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add users-admin-page`
- Docs: https://wingo-ui.com/components/users-admin-page

## Interaction

The header shows the seats in use ("8 of 10 seats", turning amber from 80%), Invite people and a menu with Export members CSV (downloads a ; separated file that opens cleanly in Excel) and Security settings. Switch between Members, Roles and permissions and Audit log with a click or the arrow keys; the panel slides the way you went. In Members, a warning lists who has no two-step verification with Send reminder; search by name or email, sort by name, role or last activity, change a role in its picker (it saves with a spinner, a failure rolls back with a toast, and the last administrator cannot be demoted), remove someone from the row menu after a confirmation (you and the owner cannot be removed), and in Invitations resend or revoke an invitation. Invite people opens the invite dialog (a bottom sheet on phones): paste or type addresses, pick a role, add a message and send; addresses already in the team are refused. With every seat taken it first offers Add 5 seats. In Roles and permissions, pick a role card (or a column heading) to highlight it, create a role with New role (name, description, copy permissions from a role), and tick permissions in the matrix: requirements follow along, changed cells get a dot, and a save bar slides up with the count, Discard and Save (mod+s while it shows). Leaving the tab with unsaved changes asks first. On phones the matrix picks one role at a time with chips and the save bar floats above the edge. In Audit log, search, filter by user, action, record type, severity or IP, change the period, expand a row for its changes, and export the rows as CSV; your own changes on this page appear at the top. Toasts confirm each action. Sections rise in once on mount; reduced motion keeps them still.

## Installation

```bash
npx wingo-ui@latest add users-admin-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/users-admin-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/users-admin-page.tsx`
- `components/blocks/users-admin-page-data.ts`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Alert](https://wingo-ui.com/components/alert) ([Markdown](https://wingo-ui.com/components/alert.md))
- [Alert Dialog](https://wingo-ui.com/components/alert-dialog) ([Markdown](https://wingo-ui.com/components/alert-dialog.md))
- [Audit Log](https://wingo-ui.com/components/audit-log) ([Markdown](https://wingo-ui.com/components/audit-log.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Card](https://wingo-ui.com/components/card) ([Markdown](https://wingo-ui.com/components/card.md))
- [Date Range Picker](https://wingo-ui.com/components/date-range-picker) ([Markdown](https://wingo-ui.com/components/date-range-picker.md))
- [Dialog](https://wingo-ui.com/components/dialog) ([Markdown](https://wingo-ui.com/components/dialog.md))
- [Filter Builder](https://wingo-ui.com/components/filter-builder) ([Markdown](https://wingo-ui.com/components/filter-builder.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Members table](https://wingo-ui.com/components/members-table) ([Markdown](https://wingo-ui.com/components/members-table.md))
- [Page Header](https://wingo-ui.com/components/page-header) ([Markdown](https://wingo-ui.com/components/page-header.md))
- [Permissions Matrix](https://wingo-ui.com/components/permissions-matrix) ([Markdown](https://wingo-ui.com/components/permissions-matrix.md))
- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Settings Layout](https://wingo-ui.com/components/settings-layout) ([Markdown](https://wingo-ui.com/components/settings-layout.md))
- [Tabs](https://wingo-ui.com/components/tabs) ([Markdown](https://wingo-ui.com/components/tabs.md))
- [Textarea](https://wingo-ui.com/components/textarea) ([Markdown](https://wingo-ui.com/components/textarea.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [Usage Meter](https://wingo-ui.com/components/usage-meter) ([Markdown](https://wingo-ui.com/components/usage-meter.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Date utils](https://wingo-ui.com/components/date-utils) ([Markdown](https://wingo-ui.com/components/date-utils.md))
- [File export](https://wingo-ui.com/components/file-export) ([Markdown](https://wingo-ui.com/components/file-export.md))
- [Filters](https://wingo-ui.com/components/filters) ([Markdown](https://wingo-ui.com/components/filters.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(admin)/team/page.tsx (the shell lives in app/(admin)/layout.tsx: <AdminShell>{children}</AdminShell>)
"use client"

import { UsersAdminPage } from "@/components/blocks/users-admin-page"

export default function TeamPage() {
  return (
    <UsersAdminPage
      companyName="Northwind Labs"
      onInvite={async ({ emails, role }) => {
        await fetch("/api/team/invitations", { method: "POST", body: JSON.stringify({ emails, role }) })
      }}
      onRoleChange={async (member, role) => {
        await fetch(`/api/team/${member.id}`, { method: "PATCH", body: JSON.stringify({ role }) })
      }}
      onPermissionsSave={async (permissions) => {
        await fetch("/api/roles", { method: "PUT", body: JSON.stringify(permissions) })
      }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `companyName?` | `string` | `"Northwind Labs"` | The workspace named in the description ("Manage who has access to Northwind Labs"). |
| `members?` | `MemberData[]` |  | The team; defaults to the eight people of Northwind Labs (USERS_ADMIN_SAMPLE.members). The member with isYou cannot change their own role or leave. |
| `invites?` | `PendingInvite[]` |  | Pending invitations; defaults to USERS_ADMIN_SAMPLE.invites (one pending, one expired). Invitations that have not expired hold a seat. |
| `roles?` | `MemberRole[]` |  | The roles a member can have: Administrator, Manager, Sales, Accounting, Read only (USERS_ADMIN_SAMPLE.roles). |
| `permissionRoles?` | `PermissionRole[]` |  | The matrix's columns, same ids as roles; the locked Administrator holds every permission (USERS_ADMIN_SAMPLE.permissionRoles). |
| `permissionGroups?` | `PermissionGroup[]` |  | Six groups (Clients, Deals, Invoices, Reports, Automations, Settings and team) with 4 to 6 permissions and their requirements (USERS_ADMIN_SAMPLE.permissionGroups). |
| `permissions?` | `PermissionsValue` |  | The saved permissions, controlled (role id to granted permission ids); pair it with onPermissionsSave. |
| `defaultPermissions?` | `PermissionsValue` |  | The saved permissions while uncontrolled; defaults to USERS_ADMIN_SAMPLE.permissions. |
| `onPermissionsSave?` | `(value: PermissionsValue) => (void \| Promise<unknown>)` |  | The save bar's Save (and mod+s). A promise keeps it saving; a rejection keeps the bar with its message. Default: a simulated request. |
| `defaultPermissionsDraft?` | `PermissionsValue` |  | Edits already pending when the page opens (the save bar showing); USERS_ADMIN_SAMPLE.permissionsDraft has three. |
| `onDirtyChange?` | `(dirty: boolean) => void` |  | The matrix differs from the saved value, or no longer does; hide a phone tab bar while true so one bar holds the bottom edge. |
| `auditEvents?` | `AuditEvent[]` |  | The audit log, newest first; defaults to 120 events over the last 30 days (USERS_ADMIN_SAMPLE.auditEvents). Changes made on this page are added on top. |
| `auditActions?` | `Record<string, AuditActionDef>` |  | Verb, tone, group and icon per action key; defaults to USERS_ADMIN_SAMPLE.auditActions with icons per key. |
| `auditTargetTypes?` | `Record<string, string>` |  | Record type names for the filter and the export (member, role, client, invoice, deal, settings, login). |
| `defaultAuditFilter?` | `FilterGroup` |  | The audit log's first filter; usersAdminAuditTypeFilter(["invoice"]) shows only invoices. |
| `auditPeriodDays?` | `number` | `7` | The audit log's period on open: the last n days up to now; 0 shows all time. |
| `seats?` | `{ used: number; total: number }` | `"{ used: 8, total: 10 }"` | Seats in the plan. Pending invitations reserve seats; with none free, inviting offers to add seats first. |
| `seatStep?` | `number` | `5` | How many seats "Add seats" adds to the plan. |
| `tab?` | `"members" \| "roles" \| "audit"` |  | The open tab, controlled; pair it with onTabChange. |
| `defaultTab?` | `"members" \| "roles" \| "audit"` | `"members"` | The tab open on mount while uncontrolled. |
| `onTabChange?` | `(tab: UsersAdminTab) => void` |  | Called on a tab switch (after the unsaved changes question, when there is one). |
| `require2fa?` | `boolean` | `true` | The workspace requires two-step verification: members without it get a warning with Send reminder. |
| `canManage?` | `boolean` | `true` | False is the read-only view: no invite, role changes, removals, new roles or matrix edits, and an info alert says why. |
| `saveShortcut?` | `string` | `"mod+s"` | Saves the matrix while it has unsaved changes; "" turns the key off. |
| `inviteOpen?` | `boolean` |  | The invite dialog, controlled (the shell's "Invite a teammate" command can open it). |
| `defaultInviteOpen?` | `boolean` | `false` | Opens the invite dialog on mount (the seats dialog when every seat is taken). |
| `onInviteOpenChange?` | `(open: boolean) => void` |  | Called when the invite dialog opens or closes. |
| `securityHref?` | `string` | `"/settings/security"` | Where "Security settings" in the header menu goes. |
| `onInvite?` | `(input: { emails: string[]; role: string; message?: string }) => (void \| Promise<unknown>)` |  | Sends invitations; the page adds them to Invitations. Default: a simulated request. |
| `onRoleChange?` | `(member: MemberData, role: string) => (void \| Promise<unknown>)` |  | A member's new role; a rejection rolls the picker back and shows its message. The last administrator cannot be demoted. |
| `onRemove?` | `(member: MemberData) => (void \| Promise<unknown>)` |  | Removes a member after the confirmation; the seat frees up. |
| `onResendInvite?` | `(invite: PendingInvite) => (void \| Promise<unknown>)` |  | Sends an invitation again and restarts its seven days. |
| `onRevokeInvite?` | `(invite: PendingInvite) => (void \| Promise<unknown>)` |  | Revokes an invitation; a rejection brings the row back. |
| `onRoleCreate?` | `(role: { name: string; description?: string; copyFrom?: string }) => (void \| Promise<unknown>)` |  | Creates a role from the New role dialog; the page adds its column with the copied permissions. A rejection shows under the name. |
| `onExport?` | `(members: MemberData[]) => (void \| Promise<unknown>)` |  | Export members CSV. Default: downloads members-<date>.csv with ; separators and a byte order mark. |
| `onRemindTwoFactor?` | `(members: MemberData[]) => (void \| Promise<unknown>)` |  | Send reminder, with the members who have no two-step verification. |
| `onAddSeats?` | `(count: number) => (void \| Promise<unknown>)` |  | Adds seatStep seats from the seats dialog; then the invite form opens. |
| `labels?` | `Partial<UsersAdminPageLabels>` | `"DEFAULT_USERS_ADMIN_PAGE_LABELS"` | Every visible string, English by default (USERS_ADMIN_PAGE_LABELS_EN); USERS_ADMIN_PAGE_LABELS_RO is the Romanian set. Nested keys carry the composed items' strings (membersTable, permissionsMatrix, auditLog, auditFilters, auditPeriod, usageMeter). |
| `locale?` | `string` | `"en-US"` | Intl locale for numbers, dates and relative times. |
| `now?` | `Date \| number` |  | The reference time for last activity, invitations and the audit period; default the live clock. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Accent of the invite button, the tabs, the selected role, the checkboxes and the save bar. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | Corner size of the role cards and the skeleton panels (12 / 16 / 24px). |
| `loading?` | `boolean` | `false` | Skeletons in the real layout: the header, the seats, the member rows, the role cards and the matrix. |
| `animateIn?` | `boolean` | `true` | The header and the panel rise in once when the page mounts on the client; off in thumbnails and under reduced motion. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` | `"<a>"` | The router link for the header's links; default the surrounding LinkProvider's (the admin shell's). |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<UsersAdminPageSlot, string>>` |  | Classes for header, seats, panels, members, twoFactor, readOnly, roles, roleList, roleCard, permissions, saveBar, audit, seatsDialog and roleDialog. |

## Parts

### DEFAULT_USERS_ADMIN_PAGE_LABELS (constant)

Every English string of the members, roles and audit log tabs, the seats bar and the save bar. Spread it and override what you translate.

### USERS_ADMIN_PAGE_LABELS_EN (constant)

The English set that DEFAULT_USERS_ADMIN_PAGE_LABELS points to, kept so older imports keep working.

### USERS_ADMIN_PAGE_LABELS_RO (constant)

A ready Romanian set of every string. Pass it as labels for a Romanian back office.

### generateUsersAdminSample (function)

Builds the demo team in English or Romanian: members, invitations, seats, roles with their permissions and the audit log.

### USERS_ADMIN_SAMPLE (constant)

The English demo team built by generateUsersAdminSample. Replace it with data from your API.

### usersAdminActor (function)

Turns a member into the actor of an audit log entry (id, name, avatar).

### usersAdminAuditTypeFilter (function)

Builds a filter group of the audit log by record type (for example only invoices), with stable ids for server rendering.

### USERS_ADMIN_NOW (constant)

The fixed "now" of the sample (September 24, 2026, 4:40 PM) that last seen and log times are measured from.

## Examples

- **Roles with 3 unsaved changes**: defaultPermissionsDraft: changed cells carry a dot and the save bar offers Discard and Save (mod+s).
- **Audit log filtered to invoices**: defaultTab="audit", a record type filter on Invoice and the last 30 days.
- **Invite with every seat taken**: 8 members and 2 pending invitations fill 10 seats: inviting offers Add 5 seats first.
- **Read only (Ryan Mitchell)**: canManage={false}: no invite, fixed roles, a read-only matrix and an info alert.
- **Loading**: loading: the header, the seats, the rows and the matrix as skeletons.
- **Brand tone, large radius**: tone="brand", radius="lg" on the roles tab.
- **Localized: Romanian**: USERS_ADMIN_PAGE_LABELS_RO, locale="ro-RO" and generateUsersAdminSample("ro"): the same page for a Romanian workspace.

## See also

- [Members table](https://wingo-ui.com/components/members-table) ([Markdown](https://wingo-ui.com/components/members-table.md))
- [Permissions Matrix](https://wingo-ui.com/components/permissions-matrix) ([Markdown](https://wingo-ui.com/components/permissions-matrix.md))
- [Audit Log](https://wingo-ui.com/components/audit-log) ([Markdown](https://wingo-ui.com/components/audit-log.md))
- [Admin Shell](https://wingo-ui.com/components/admin-shell) ([Markdown](https://wingo-ui.com/components/admin-shell.md))
- [Settings Layout](https://wingo-ui.com/components/settings-layout) ([Markdown](https://wingo-ui.com/components/settings-layout.md))

---

Canonical: https://wingo-ui.com/components/users-admin-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
