# Role Management UI in React: Mitglieder, Rollen, Audit-Log

> Bau eine Role Management UI in React für B2B SaaS: Mitglieder und Einladungen mit Seats, Berechtigungsmatrix mit Save Bar, Audit-Log und Smartphone-Layout.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 5. Nov. 2026
- Kategorie: [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build)
- Lesezeit: 9 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/role-management-ui
- Englische Fassung: https://wingo-ui.com/blog/role-management-ui

## Kurz gesagt

Eine Role Management UI für ein B2B-SaaS braucht drei Tabs: Mitglieder und Einladungen, die gegen deine Seats zählen, Rollen, die du in einer Berechtigungsmatrix bearbeitest und über eine einzige Save Bar speicherst, und ein Audit-Log, das zeigt, wer was geändert hat. In React setzt du sie aus dem Block Users Admin Page von Wingo UI zusammen, übergibst für jede Änderung eine Server Action und validierst die Berechtigungen auf dem Server noch einmal. Auf dem Smartphone werden die Mitglieder zu Karten, und die Matrix zeigt jeweils eine Rolle.

Jedes B2B-Produkt erreicht irgendwann den Tag, an dem ein Kunde fragt: „Kann meine Buchhalterin Rechnungen sehen, aber keine Kunden löschen?“ Für diese Frage brauchst du eine Role Management UI: einen Team-Screen, auf dem ein Admin Leute einlädt, jedem eine Rolle gibt, festlegt, was jede Rolle darf, und später nachsehen kann, wer was geändert hat. Wer danach sucht, findet Block-Galerien mit einer Mitgliedertabelle und einem Einladungsdialog und Dribbble-Shots von einem Raster aus Checkboxen. Kaum jemand erklärt, wie die Teile zusammenspielen: Seats, Abhängigkeiten zwischen Berechtigungen, ungespeicherte Änderungen, der Audit-Trail und das Layout auf dem Smartphone. Dieses Tutorial baut den Screen in Next.js aus Teilen von Wingo UI, mit dem Code, den du dafür brauchst.

## Was gehört in eine User Role Management UI?

Drei Tabs decken ab, was Admins wirklich tun, und zwar in der Reihenfolge, in der sie es tun:

1. **Mitglieder und Einladungen.** Das Team mit einem Rollen-Picker pro Zeile, Status, letzter Aktivität und dem Stand der Zwei-Faktor-Anmeldung, dazu die offenen Einladungen. Oben sitzt eine Seat-Anzeige, denn beim Einladen stößt man an die Grenzen des Plans.
2. **Rollen und Berechtigungen.** Eine Karte pro Rolle und eine Matrix aus Rollen und Berechtigungen, gruppiert nach Modul (Kunden, Deals, Rechnungen, Berichte, Einstellungen und Team). Eine Save Bar sammelt die Änderungen.
3. **Audit-Log.** Wer hat wann was an welchem Datensatz getan, von welcher IP aus, mit Filtern und einem CSV-Export für den Sicherheitsfragebogen, den dein Kunde früher oder später schickt.

Ob Admins dem Screen vertrauen, entscheiden die Details. Der letzte Administrator lässt sich nicht herabstufen, niemand entfernt den Owner oder sich selbst, und eine gescheiterte Anfrage stellt den alten Wert wieder her und sagt das auch. Eine User Role Management UI, die Erfolg meldet und die Änderung dann still verliert, ist schlimmer als gar keine.

## Block aus einer Galerie oder kompletter Admin-Screen?

Starte mit einem Block, wenn die Rollen feststehen, und mit einem kompletten Screen, sobald Kunden Rollen selbst bearbeiten können. Die meisten Treffer für ein React User Management Template sind Blöcke: eine Mitgliedertabelle und ein Einladungsdialog. Die Angaben stammen von der jeweiligen öffentlichen Seite, Stand November 2026:

| Option | Was drin ist | Preis |
|---|---|---|
| [shadcnblocks „User Table with Dialog Invite“](https://www.shadcnblocks.com/block/invite-user1) | Mitgliedertabelle (Name, E-Mail, Rolle, Status), Einladungsdialog für mehrere Adressen mit Rollenauswahl | Kostenlos |
| [shadcn.io „Account Team Members“](https://www.shadcn.io/blocks/account-team-members) | Rollen pro Person, offene Einladungen mit erneutem Senden und Zurückziehen, Seat-Zähler, Hinweise zur 2FA, Bestätigung vor dem Entfernen | Pro-Plan |
| [Wingo UI Users Admin Page](https://wingo-ui.com/components/users-admin-page) | Mitglieder und Einladungen mit Seats, Berechtigungsmatrix mit Save Bar, Audit-Log, Layouts fürs Smartphone | Pro |

Hat dein Produkt drei feste Rollen und wird nie ein Kunde eigene verlangen, reicht die kostenlose Tabelle von shadcnblocks: Eine Rollenauswahl pro Zeile ist dann das ganze Feature. Sobald Rollen bearbeitbar werden, brauchst du die Matrix, das Prinzip aus Entwurf und Speichern und die Historie. Genau diesen Screen baut dieser Artikel. Die Teile von Wingo UI sind Pro; die [Preise](https://wingo-ui.com/de/pricing) beginnen bei $8 im Monat.

## Wie baust du die Team-Seite in Next.js auf?

Leg den Team-Screen in eine einzige Route: eine Server-`page.tsx`, die die Daten lädt, eine Client-View, die die Handler verdrahtet, und einen Katalog der Berechtigungen, den beide Seiten importieren. Die Seite liegt in der Admin-Route-Group aus dem [Tutorial zum Next.js Admin Dashboard](https://wingo-ui.com/de/blog/nextjs-admin-dashboard-tutorial), das die [Admin Shell](https://wingo-ui.com/components/admin-shell) und das Modul `server/dal.ts` mit `requireAdmin()` einrichtet. Installier den Block (er bringt die Tabelle, die Matrix und das Log mit):

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

```text
app/(admin)/team/
  page.tsx          Server: lädt Mitglieder, Einladungen, Rollen, Rechte, Audit-Zeilen
  team-view.tsx     Client: UsersAdminPage + Server Actions
  permissions.ts    der Berechtigungskatalog, gemeinsam für View und Server
  actions.ts        invite, changeRole, removeMember, revokeInvite, savePermissions
```

Halt den Berechtigungskatalog in einem einfachen Modul ohne `"use client"`. Die Matrix rendert ihn, und der Server validiert gegen dieselbe Liste. So können die beiden nicht auseinanderlaufen:

```ts
// app/(admin)/team/permissions.ts
import type { PermissionGroup, PermissionRole } from "@/components/ui/permissions-matrix";

export const PERMISSION_GROUPS: PermissionGroup[] = [
  {
    id: "clients",
    label: "Kunden",
    permissions: [
      { id: "clients.view", label: "Kunden ansehen" },
      { id: "clients.edit", label: "Kunden bearbeiten", requires: ["clients.view"] },
      { id: "clients.export", label: "Kunden exportieren", requires: ["clients.view"], dangerous: true },
    ],
  },
  {
    id: "invoices",
    label: "Rechnungen",
    permissions: [
      { id: "invoices.view", label: "Rechnungen ansehen" },
      { id: "invoices.issue", label: "Rechnungen ausstellen", requires: ["invoices.view"] },
      { id: "invoices.cancel", label: "Rechnungen stornieren", requires: ["invoices.issue"], dangerous: true },
    ],
  },
];

// der Administrator hat jede Berechtigung und lässt sich nicht bearbeiten
export const PERMISSION_ROLES: PermissionRole[] = [
  { id: "admin", name: "Administrator", locked: true },
  { id: "sales", name: "Vertrieb" },
  { id: "accounting", name: "Buchhaltung" },
  { id: "viewer", name: "Nur lesen" },
];
```

Lass die id des Administrators bei `admin`: Der Schutz für den letzten Administrator im Block sucht genau nach dieser Rolle.

Die View ist eine schlanke Client-Komponente, die die Ergebnisse der Server Actions in die Promises übersetzt, die der Block erwartet. Wird ein Promise abgelehnt, rollt der Block die Änderung zurück und zeigt die Meldung an:

```tsx
// app/(admin)/team/team-view.tsx
"use client";

import { UsersAdminPage, type UsersAdminPageProps } from "@/components/blocks/users-admin-page";
import { PERMISSION_GROUPS, PERMISSION_ROLES } from "./permissions";
import { changeRole, invite, removeMember, revokeInvite, savePermissions } from "./actions";

type Props = Pick<
  UsersAdminPageProps,
  "companyName" | "members" | "invites" | "roles" | "defaultPermissions" | "auditEvents" | "seats" | "canManage"
>;

async function check(result: Promise<{ error?: string }>) {
  const { error } = await result;
  if (error) throw new Error(error);
}

export function TeamView(props: Props) {
  return (
    <UsersAdminPage
      {...props}
      permissionRoles={PERMISSION_ROLES}
      permissionGroups={PERMISSION_GROUPS}
      onInvite={(input) => check(invite(input))}
      onRoleChange={(member, role) => check(changeRole(member.id, role))}
      onRemove={(member) => check(removeMember(member.id))}
      onRevokeInvite={(item) => check(revokeInvite(item.id))}
      onPermissionsSave={(value) => check(savePermissions(value))}
    />
  );
}
```

`page.tsx` lädt die Zeilen, berechnet `canManage` aus der Session und rendert `<TeamView />`. Mit `canManage={false}` wird die Seite schreibgeschützt und erklärt, warum. Das ist besser, als das Team vor Leuten zu verstecken, die es nur sehen müssen.

## Wie geht der Mitglieder-Tab mit Einladungen und Seats um?

Zähl offene Einladungen als belegte Seats, denn beim Einladen geht dem Plan der Platz aus. Im Block belegt eine Einladung einen Seat, bis sie abläuft: Die Anzeige unter dem Titel zeigt die belegten Seats mit einer Zeile wie „2 seats held by invitations“ und wechselt ab 80% in ihre Warnfarbe. Wenn Mitglieder und Einladungen alle Seats belegen, bietet „Invite people“ zuerst an, Seats hinzuzufügen (`seatStep`, standardmäßig fünf, über `onAddSeats`), und öffnet erst danach das Formular.

Eine gute Invite Team Members UI nimmt an, was Leute einfügen: Die [Members Table](https://wingo-ui.com/components/members-table) zerlegt eine Liste wie `Emma Carter <emma@northwind.app>; daniel@northwind.app` in einzelne Adressen und listet die abgelehnten unter „Not added“ mit Grund auf (ungültig, schon Mitglied, schon eingeladen). Sind es mehr Adressen als freie Seats, bleibt „Send“ deaktiviert, und die Tabelle sagt, warum.

Rollenwechsel in einer Zeile sind optimistisch: Der Picker zeigt die neue Rolle mit einem Spinner, und bei einer Ablehnung springt sie samt Fehlermeldung zurück. Der Block verweigert das Herabstufen des letzten Administrators, bevor er dich überhaupt aufruft. Deine Action `changeRole` muss es trotzdem ebenfalls verweigern, denn eine Server Action lässt sich auch ohne den Button aufrufen.

> Live-Demo (Users Admin Page): Ändere die Rolle eines Mitglieds, wechsle dann zu „Roles and permissions“ und hak ein paar Zellen an: Die Save Bar zählt deine Änderungen. Probier sie unter [Users Admin Page](https://wingo-ui.com/components/users-admin-page) aus und installier sie mit `npx wingo-ui@latest add users-admin-page`.

## Wie gestaltest du eine Permissions Matrix UI, die man lesen kann?

Eine Permissions Matrix UI scheitert auf zwei Arten: zu viele Zeilen zum Überfliegen und Checkboxen, die Zustände erlauben, die dein Backend ablehnt. Die [Permissions Matrix](https://wingo-ui.com/components/permissions-matrix) löst beides mit ein paar Regeln, die sich auch dann lohnen, wenn du deine eigene baust.

- **Nach Modul gruppieren, darin die Verben.** Die Zeilen sind gruppiert (Kunden, Rechnungen), mit einer Gruppen-Checkbox, die einen Strich zeigt, wenn nur ein Teil aktiv ist. Nutz in allen Modulen dieselben Verben (ansehen, bearbeiten, exportieren), damit sich eine Zeile in jeder Gruppe gleich liest.
- **Abhängigkeiten festlegen.** `requires: ["invoices.view"]` auf „Rechnungen ausstellen“ heißt: Wer das Recht bekommt, bekommt auch „Rechnungen ansehen“, und wer „Rechnungen ansehen“ verliert, verliert auch „Rechnungen ausstellen“. Die Zellen, die sich dadurch von selbst geändert haben, blinken einmal auf, und Screenreader sagen die Änderung an.
- **Gefährliche Rechte markieren.** `dangerous: true` setzt einen Warnpunkt an Exporte und Löschungen, also an genau die Berechtigungen, nach denen ein Security-Review fragt.
- **Die oberste Rolle sperren.** Eine Spalte mit `locked` hat alle Rechte und erklärt das bei Hover, Fokus oder langem Druck. So wundert sich niemand, warum sich der Administrator nicht bearbeiten lässt.
- **Den Diff zeigen.** Gib den gespeicherten Wert als `compareTo` mit, dann bekommen abweichende Zellen einen Punkt und eine Tönung, und die Toolbar zeigt ihre Anzahl.

Für Rollen, die mehr als Ja oder Nein brauchen, macht `mode="levels"` aus jeder Zelle eine Auswahl aus None / View / Edit / Full.

> Live-Demo (Permissions Matrix): Gib einer Rolle „Issue invoices“: „View invoices“ wird mit eingeschaltet, und die geänderten Zellen bekommen einen Punkt. Probier sie unter [Permissions Matrix](https://wingo-ui.com/components/permissions-matrix) aus und installier sie mit `npx wingo-ui@latest add permissions-matrix`.

### Warum eine Save Bar, statt jede Checkbox einzeln zu speichern?

Weil eine geänderte Berechtigung jedes Mitglied mit dieser Rolle betrifft. Landen die Änderungen in einem Entwurf, kann ein Admin eine Rolle komplett einrichten, die Anzahl sehen („3 unsaved changes“) und einmal speichern oder alles verwerfen. Der Block zählt mit `countPermissionChanges(draft, saved, { ignoreRoles })` und nutzt eine Save Bar aus dem [Settings Layout](https://wingo-ui.com/components/settings-layout), die mit Verwerfen und Speichern hochgleitet, dazu `mod+s`, solange sie sichtbar ist. Wer mit ungespeicherten Änderungen den Tab wechselt, wird vorher gefragt. Die Rolle eines einzelnen Mitglieds speichert dagegen pro Klick: Das ist eine klare Entscheidung über eine Person.

## Was wird aus der Matrix auf dem Smartphone?

Sie zeigt jeweils eine Rolle. Bei 390px passt ein Raster aus fünf Rollen und zwanzig Berechtigungen nicht hinein, und seitliches Scrollen versteckt die Rollennamen, während du Häkchen setzt. Deshalb scrollt die Matrix im Standardlayout auf dem Smartphone nicht seitwärts. Unter 768px bietet sie über `mobileLayout` zwei Layouts: `"accordion"` (eine Karte pro Rolle mit einem Switch pro Berechtigung und pro Modul) oder `"role-picker"` (Rollen-Chips über einer einzigen Liste). Die Users Admin Page nutzt den Rollen-Picker, weil Admins meist eine Rolle nach der anderen bearbeiten.

Stapel nie zwei Leisten am selben Rand. Die Save Bar schwebt über dem unteren Rand und respektiert die Safe Area, und `onDirtyChange` sagt deiner Shell, wann sie ihre Tab Bar auf dem Smartphone ausblenden soll, solange Änderungen offen sind. Der Mitglieder-Tab folgt derselben Regel wie jede [responsive Tabelle](https://wingo-ui.com/de/blog/responsive-table-mobile-cards): Zeilen werden zu Karten mit einem Rollen-Picker in voller Breite, und Einladungsdialog und Menüs öffnen sich als Bottom Sheets.

## Was sollte das Audit-Log festhalten?

Jede Änderung, die ein Admin auf diesem Screen macht, und die Sicherheitsereignisse drumherum: verschickte und angenommene Einladungen, Rollenwechsel, Entfernungen, Änderungen an den Berechtigungen einer Rolle, fehlgeschlagene Anmeldungen und Exporte. Jedes Event im [Audit Log](https://wingo-ui.com/components/audit-log) trägt `actor`, `action`, `target`, `severity`, `ip`, `device` und eine Liste `changes` mit Vorher- und Nachher-Werten, die die Zeile beim Aufklappen als Diff zeigt.

Schreib diese Zeilen in derselben Transaktion wie die Änderung. Der Block setzt die Änderungen dieser Seite als sofortiges Feedback oben ins Log, aber die Quelle der Wahrheit ist deine Tabelle. Der Audit-Tab des Blocks filtert und exportiert die `auditEvents`, die du übergibst, im Browser. Lade also ein begrenztes Zeitfenster, etwa die letzten 90 Tage. Für ein Log, das dafür zu groß ist, renderst du das [Audit Log](https://wingo-ui.com/components/audit-log) selbst mit `mode="server"`, `onQueryChange`, `hasMore` und `onLoadMore` und gibst ihm ein `onExport`, das die CSV auf dem Server baut statt aus den Zeilen auf dem Bildschirm.

> Live-Demo (Audit Log): Filter das Log nach Person oder Aktion, klapp eine Zeile für ihre Vorher- und Nachher-Werte auf und exportier die Zeilen dann als CSV. Probier sie unter [Audit Log](https://wingo-ui.com/components/audit-log) aus und installier sie mit `npx wingo-ui@latest add audit-log`.

## Wo setzt du die Berechtigungen durch?

Auf dem Server, und zwar jedes Mal. Die Matrix verhindert unmögliche Zustände im Browser, und `savePermissions` prüft sie gegen denselben Katalog noch einmal:

```ts
// app/(admin)/team/actions.ts
"use server";

import type { PermissionsValue } from "@/components/ui/permissions-matrix";
import { requireAdmin } from "@/server/dal";
import { replaceRolePermissions } from "@/server/team";
import { PERMISSION_GROUPS, PERMISSION_ROLES } from "./permissions";

const REQUIRES = new Map(
  PERMISSION_GROUPS.flatMap((group) => group.permissions.map((p) => [p.id, p.requires ?? []] as const)),
);
const EDITABLE = new Set(PERMISSION_ROLES.filter((role) => !role.locked).map((role) => role.id));

export async function savePermissions(value: PermissionsValue): Promise<{ error?: string }> {
  const admin = await requireAdmin();
  const grants: Record<string, string[]> = {};
  for (const [roleId, granted] of Object.entries(value)) {
    if (!EDITABLE.has(roleId)) continue; // gesperrte Rollen werden nie geschrieben
    if (!Array.isArray(granted)) return { error: "Unerwartetes Format der Berechtigungen" };
    const set = new Set(granted);
    for (const id of granted) {
      const needs = REQUIRES.get(id);
      if (!needs) return { error: `Unbekannte Berechtigung ${id}` };
      const missing = needs.find((need) => !set.has(need));
      if (missing) return { error: `${id} braucht ${missing}` };
    }
    grants[roleId] = granted;
  }
  // prüft, ob jede Rollen-id zum Workspace gehört, und schreibt dann in einer Transaktion die Rechte und pro geänderter Rolle eine Audit-Zeile role.updated
  return replaceRolePermissions(admin.workspaceId, admin.userId, grants);
}
```

Die Helfer `resolvePermissionDependencies` und `countPermissionChanges` werden ebenfalls exportiert, allerdings aus einem Client-Modul. Der Server liest deshalb den einfachen Katalog. Dein Data Layer prüft dann pro Anfrage eine Berechtigung (`can(user, "invoices.cancel")`) statt eines Rollennamens. So funktioniert eine umbenannte oder neue Rolle ohne Codeänderung.

## Wie geht es von hier aus weiter?

Wenn du nur Mitglieder, Einladungen und eine Rolle pro Person brauchst, starte mit der [Members Table](https://wingo-ui.com/components/members-table). Für den ganzen Team-Screen öffne die Demo der [Users Admin Page](https://wingo-ui.com/components/users-admin-page): Probier einen Rollenwechsel, eine Änderung in der Matrix und die Filter des Audit-Logs aus und installier sie dann mit `npx wingo-ui@latest add users-admin-page`. Jede Demo und ihre Props sind ohne Konto öffentlich; den Quellcode dieser vier Teile gibt es mit Pro. Den Rest des Admin-Bereichs findest du in der Kategorie [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build), darunter das [Tutorial zur Settings-Seite](https://wingo-ui.com/de/blog/react-settings-page), das dieselbe Save Bar nutzt.

## Komponenten in diesem Artikel

- [Users Admin Page](https://wingo-ui.com/components/users-admin-page) (Pro): A React users admin page: team members and invitations with seats, roles with a permissions matrix and a save bar, and the audit log. Installation: `npx wingo-ui@latest add users-admin-page`
- [Permissions Matrix](https://wingo-ui.com/components/permissions-matrix) (Pro): A React permissions matrix of roles by permissions, grouped by module, with dependencies, unsaved-change dots and a per-role list on phones. Installation: `npx wingo-ui@latest add permissions-matrix`
- [Members table](https://wingo-ui.com/components/members-table) (Pro): A React members table for B2B team settings: a role select per row, status, activity, 2FA, pending invitations, seats and an invite dialog. Installation: `npx wingo-ui@latest add members-table`
- [Audit Log](https://wingo-ui.com/components/audit-log) (Pro): A React audit log table: who did what, when and to which record, with filters, day groups, before / after diffs and a CSV export. Installation: `npx wingo-ui@latest add audit-log`

## Häufige Fragen

### Was ist eine Role Management UI?

Das ist der Admin-Screen, auf dem der Owner eines Workspaces sieht, wer Zugriff hat, Leute einlädt, jeder Person eine Rolle gibt und festlegt, was jede Rolle darf. Die meisten B2B-Produkte zeigen auf demselben Screen auch die Historie dieser Änderungen in einem Audit-Log.

### Sollte eine Berechtigungsmatrix jede Checkbox sofort speichern?

Meist nicht. Änderungen an Berechtigungen betreffen viele Leute auf einmal, also sammelst du sie in einem Entwurf, markierst die geänderten Zellen und speicherst alles zusammen über eine Save Bar mit Verwerfen daneben. Speichern pro Klick passt für die Rolle eines einzelnen Mitglieds, denn das ist eine klare Entscheidung.

### Wie zeige ich eine Berechtigungsmatrix auf dem Smartphone?

Lass bei 390px kein Raster aus fünf Rollen seitwärts scrollen. Zeig jeweils eine Rolle, entweder als Akkordeon-Karte pro Rolle oder als Rollen-Chips über einer Liste von Switches, und fixier die Save Bar knapp über dem unteren Rand.

### Reicht es, Buttons auszublenden, um Rollen durchzusetzen?

Nein. Die Oberfläche erklärt die Regeln nur. Jede Server Action und jede API-Route muss die Rolle des Aufrufers prüfen und die übermittelten Berechtigungen validieren, denn jeder kann die Anfrage auch ohne deine Buttons senden.

### Zählen offene Einladungen als Seats?

Das sollten sie, bis sie ablaufen oder zurückgezogen werden. Sonst kann ein Admin mehr Einladungen verschicken, als der Plan erlaubt, und wer zuletzt annimmt, bekommt einen Fehler.

---

Quelle: https://wingo-ui.com/de/blog/role-management-ui
