# React Settings Page: Navigation, Save Bar und Danger Zone

> Bau eine React Settings Page mit Seitennavigation am Desktop, iOS-Liste auf dem Smartphone, Save Bar für offene Änderungen und abgesicherter Danger Zone.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 16. Okt. 2026
- Kategorie: [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build)
- Lesezeit: 11 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-settings-page
- Englische Fassung: https://wingo-ui.com/blog/react-settings-page

## Kurz gesagt

Eine gute React Settings Page teilt die Einstellungen in Bereiche auf: links eine Navigation auf dem Desktop, auf dem Smartphone eine Liste wie in iOS, die jeden Bereich hereinschiebt. Textfelder sammeln sich in einem Entwurf, den eine Save Bar speichert oder verwirft, Switches speichern selbst und springen zurück, wenn die Anfrage scheitert, und destruktive Aktionen stehen ganz am Ende in einer Danger Zone hinter einer getippten Bestätigung. Mit dem Settings Layout von Wingo UI ist das eine Komponente plus deine eigenen Felder und Save-Handler.

Jede App bekommt irgendwann eine Einstellungsseite, und meistens entsteht sie zuletzt und in Eile: ein langes Formular, ganz unten ein Speichern-Button, zu dem niemand scrollt, Switches, die gespeichert aussehen, es aber nicht sind, und ein Button „Konto löschen“ direkt neben „Passwort ändern“. Dieses Tutorial baut Schritt für Schritt eine React Settings Page, die all das vermeidet: eine Navigation auf dem Desktop, eine iOS-Liste mit Push-Navigation auf dem Smartphone, eine Save Bar, die nur bei Änderungen erscheint, Switches, die selbst speichern, und am Ende eine Danger Zone. Wir bauen Wingo UI, und der Code nutzt unser [Settings Layout](https://wingo-ui.com/components/settings-layout). Wir sind also nicht neutral; die Muster funktionieren mit jeder Bibliothek.

## Was braucht eine gute Account Settings Page UI?

Eine gute Account Settings Page UI braucht fünf Dinge: Bereiche mit eigener Adresse, ein sichtbares Speichermodell, Umschalter, die sofort greifen, eine eigene Danger Zone und ein echtes Smartphone-Layout. Jedes davon stammt aus einem Produkt, das viele schon täglich nutzen:

- **Bereiche mit eigener Adresse.** Profil, Benachrichtigungen, Abrechnung, Sicherheit. Eine linke Spalte, wenn es viele sind, Tabs bei wenigen. Support-Links sollen im richtigen Bereich landen, also gehört der offene Bereich in die URL.
- **Ein Speichermodell, das man sieht.** Shopifys [Save Bar API](https://shopify.dev/docs/api/app-home/latest/apis/user-interface-and-interactions/save-bar-api) zeigt Speichern und Verwerfen nur, solange ein Formular ungespeicherte Änderungen hat, und ihre Methode `leaveConfirmation()` fragt den Händler, bevor die App wegnavigiert (Stand Oktober 2026). Das ist das Modell für Textfelder.
- **Umschalter, die sofort greifen.** Ein Switch, der auf einen Speichern-Button wartet, lügt über seinen Zustand. Er soll sofort speichern und zurückspringen, wenn die Anfrage scheitert.
- **Destruktive Aktionen zuletzt.** Ein eigener Block mit rotem Rahmen, Buttons statt Switches und eine Bestätigung, die eine bewusste Handlung verlangt, etwa ein getipptes Wort.
- **Ein Smartphone-Layout.** Auf einem 390px breiten Bildschirm passt keine Sidebar. Die iOS-Einstellungen haben das längst gelöst: eine gruppierte Liste der Bereiche, und ein Tipp schiebt den Bereich herein.

Viele Vorlagen für eine React Einstellungsseite decken den ersten Punkt ab und hören dann auf. [Die Settings Pages von Untitled UI](https://www.untitledui.com/react/components/settings-pages) etwa listen 21 Layouts (Profil, Passwort, Team, Abrechnung, Benachrichtigungen, Integrationen), und Stand Oktober 2026 erwähnt die Seite weder ungespeicherte Änderungen noch eine Danger Zone oder ein Smartphone-Layout. Genau diese Teile kosten Zeit, deshalb bekommen sie hier den meisten Platz.

## Wie richtest du das Settings Layout in React ein?

Du richtest ein einziges `SettingsLayout` ein, das deine Bereiche als Daten bekommt und Navigation und Save Bar selbst verwaltet. Installier es zusammen mit den Controls:

```bash
npx wingo-ui@latest add settings-layout switch input alert-dialog
```

Die Settings Layout Komponente ist der Rahmen: Sie nimmt eine Liste von `sections`, rendert die Navigation, blendet die aktive Seite kurz über und verwaltet die Save Bar. Jeder Bereich ist ein Objekt mit `id`, `label`, optionalem `icon`, `group` (eine kleine Gruppenüberschrift in der Sidebar und eine eingerückte Gruppe auf dem Smartphone), `description` (der Untertitel in der Smartphone-Liste), `tone` (die Icon-Kachel auf dem Smartphone) und `content`. Innerhalb eines Bereichs kombinierst du `SettingsSection` (eine Gruppe von Zeilen mit Titel) und `SettingsRow` (Label, Beschreibung und rechts ein Control). Eine Zeile verbindet ihr Label selbst mit dem Control, ein Tipp auf den Label-Text schaltet also den Switch um.

Hier die Client-Ansicht mit drei Bereichen, einer Save Bar für die Textfelder und Switches, die selbst speichern:

```tsx
// app/(app)/settings/settings-view.tsx
"use client"

import { Bell, TriangleAlert, User } from "lucide-react"
import { useState } from "react"
import { Input } from "@/components/ui/input"
import { SettingsLayout, SettingsRow, SettingsSection } from "@/components/ui/settings-layout"
import { Switch } from "@/components/ui/switch"
import { DangerZone } from "./danger-zone"

export type AccountSettings = { name: string; title: string; digest: boolean; mentions: boolean }
type Preference = "digest" | "mentions"

export function SettingsView({ initial, section }: { initial: AccountSettings; section: string }) {
  const [saved, setSaved] = useState(initial)
  const [draft, setDraft] = useState(initial)
  const [prefError, setPrefError] = useState<Partial<Record<Preference, string>>>({})
  // nur Textfelder machen die Seite dirty; Switches speichern selbst
  const dirty = draft.name !== saved.name || draft.title !== saved.title

  const savePreference = async (key: Preference, value: boolean) => {
    setPrefError((e) => ({ ...e, [key]: undefined }))
    const res = await fetch("/api/settings/preferences", {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ [key]: value }),
    })
    if (!res.ok) {
      // die Zeile zeigt das an; die Ablehnung lässt den Switch zurückspringen
      setPrefError((e) => ({ ...e, [key]: "Speichern fehlgeschlagen. Versuch es noch mal." }))
      throw new Error("Einstellung nicht gespeichert")
    }
    setSaved((s) => ({ ...s, [key]: value }))
    setDraft((d) => ({ ...d, [key]: value }))
  }

  return (
    <SettingsLayout
      title="Einstellungen"
      description="Dein Profil und wie wir dich erreichen"
      defaultValue={section}
      onValueChange={(id) => {
        // andere Query-Parameter behalten, nur den Bereich tauschen
        const url = new URL(window.location.href)
        url.searchParams.set("section", id)
        window.history.replaceState(null, "", url)
      }}
      dirty={dirty}
      onSave={async () => {
        const res = await fetch("/api/settings/profile", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: draft.name, title: draft.title }),
        })
        if (!res.ok) throw new Error("Dein Profil konnte nicht gespeichert werden. Versuch es noch mal.")
        setSaved(draft)
      }}
      onDiscard={() => setDraft(saved)}
      sections={[
        {
          id: "profile",
          label: "Profil",
          group: "Konto",
          icon: <User />,
          tone: "info",
          description: draft.name,
          content: (
            <SettingsSection title="Öffentliches Profil" description="Für deine Teammitglieder sichtbar.">
              <SettingsRow label="Vollständiger Name" controlWidth="md">
                <Input value={draft.name} onValueChange={(name) => setDraft({ ...draft, name })} autoComplete="name" />
              </SettingsRow>
              <SettingsRow label="Position" controlWidth="md">
                <Input value={draft.title} onValueChange={(title) => setDraft({ ...draft, title })} />
              </SettingsRow>
            </SettingsSection>
          ),
        },
        {
          id: "notifications",
          label: "Benachrichtigungen",
          group: "Konto",
          icon: <Bell />,
          tone: "warning",
          description: "E-Mail-Zusammenfassung und Erwähnungen",
          content: (
            <SettingsSection title="E-Mail">
              <SettingsRow label="Tägliche Zusammenfassung" description="Neue Deals und überfällige Rechnungen, um 8 Uhr." error={prefError.digest}>
                <Switch checked={saved.digest} onCheckedChange={(v) => savePreference("digest", v)} />
              </SettingsRow>
              <SettingsRow label="Erwähnungen" description="Wenn dich jemand in einer Notiz markiert." error={prefError.mentions}>
                <Switch checked={saved.mentions} onCheckedChange={(v) => savePreference("mentions", v)} />
              </SettingsRow>
            </SettingsSection>
          ),
        },
        {
          id: "danger",
          label: "Danger Zone",
          icon: <TriangleAlert />,
          danger: true,
          description: "Konto löschen",
          content: <DangerZone />,
        },
      ]}
    />
  )
}
```

Auf drei Details kommt es an. `dirty` vergleicht den Entwurf mit den zuletzt gespeicherten Werten: Tippst du einen Namen und löschst die Änderung wieder, verschwindet die Bar. `onSave` gibt ein Promise zurück: Solange es läuft, dreht sich der Button, ein aufgelöstes Promise blendet „Changes saved“ ein und schiebt die Bar weg, und ein geworfener Fehler lässt die Bar stehen, schüttelt sie und zeigt deine Meldung. `onDiscard` stellt die gespeicherten Werte wieder her, und das Layout ruft es auch auf, wenn jemand bestätigt, einen Bereich mit ungespeicherten Änderungen zu verlassen.

> Live-Demo (Settings Layout): Tipp auf Profil, um den Bereich hereinzuschieben, ändere den Namen und sieh zu, wie die Save Bar mit Verwerfen und Speichern hochgleitet, dann tipp auf den Zurück-Button. In einem so schmalen Rahmen sind die Bereiche eine Liste wie in iOS; ab 768px zeigt dieselbe Komponente eine Sidebar. Probier sie unter [Settings Layout](https://wingo-ui.com/components/settings-layout) aus und installier sie mit `npx wingo-ui@latest add settings-layout`.

## Wie bekommt jeder Bereich einer Next.js Settings Page seine URL?

Lies den Bereich in der Server-Page aus dem Query-String und schreib ihn auf dem Client mit `history.replaceState` zurück. In Next.js 16 ist `searchParams` ein Promise, also brauchst du `await`:

```tsx
// app/(app)/settings/page.tsx
import { getAccountSettings } from "@/server/settings" // deine Datenschicht
import { SettingsView } from "./settings-view"

export default async function SettingsPage({
  searchParams,
}: {
  searchParams: Promise<{ section?: string }>
}) {
  const [{ section }, settings] = await Promise.all([searchParams, getAccountSettings()])
  return <SettingsView initial={settings} section={section ?? "profile"} />
}
```

Die Ansicht oben aktualisiert die Adresse mit `window.history.replaceState` in `onValueChange`. Next.js [unterstützt die native History API](https://nextjs.org/docs/app/getting-started/linking-and-navigating#native-history-api) und hält `usePathname` und `useSearchParams` damit synchron. Der Aufruf schreibt die Adresse ohne Navigation um, ein Wechsel des Bereichs lädt die Einstellungen also nie neu und führt die Server-Page nicht erneut aus. Weil die URL aus `window.location.href` entsteht, bleiben andere Query-Parameter erhalten, die ein nackter `?section=`-String verwerfen würde. Eine unbekannte ID in der URL fällt auf den ersten aktivierten Bereich zurück.

Einen Kompromiss auf dem Smartphone solltest du kennen. Ein Query-Parameter wählt den Bereich, aber auf dem Smartphone öffnet das Layout trotzdem die gruppierte Liste, so wie die iOS-Einstellungen. Wenn Deep Links auf dem Smartphone direkt den Bereich öffnen sollen, gib jedem Bereich ein `href` und mach ihn zu einer echten Route (`/settings/notifications`), wobei du `value` über den Pfad steuerst: Das Layout öffnet einen gerouteten Bereich dann schon hereingeschoben. Bereiche mit `href` rendern `children` statt `content`, und um ungespeicherte Änderungen über Routen hinweg muss sich dann dein Router kümmern.

## Wie sollen Switches auf einer Einstellungsseite speichern?

Sofort, mit Rollback. Der [Switch](https://wingo-ui.com/components/switch) behandelt ein Promise aus `onCheckedChange` als Speichervorgang: Er schaltet sofort um, zeigt im Thumb einen kleinen Spinner, ignoriert Klicks, bis das Promise abgeschlossen ist, und springt zurück, wenn es abgelehnt wird. Deshalb wirft `savePreference` in der Ansicht bei einer gescheiterten Antwort und aktualisiert `saved` erst nach dem Erfolg. `checked` bleibt an den gespeicherten Wert gebunden: Während die Anfrage läuft, zeigt der Switch den ausstehenden Wert, danach folgt er wieder `checked` und behauptet nie einen Zustand, den der Server nicht hat.

Bei der Fehlermeldung stolpern viele. Ein Switch mit eigenem `label` zeigt seinen `errorText` nach einem gescheiterten Speichern unter dem Label an, aber in einer `SettingsRow` hat der Switch kein eigenes Label: Das liefert die Zeile. Ein nackter Switch schüttelt sich trotzdem, springt zurück und sagt `errorText` für Screenreader an, doch sichtbar erklärt nichts, warum. Deshalb führt die Ansicht eine `prefError`-Map und gibt sie an die Prop `error` der Zeile weiter. Die rendert die Meldung unter der Beschreibung und verknüpft sie per `aria-describedby` mit dem Switch. Lösch den Eintrag zu Beginn jedes Versuchs, damit ein erfolgreicher neuer Versuch die Meldung entfernt.

Wichtig ist, Switches aus `dirty` herauszuhalten. Würde ein Umschalter auch die Save Bar einblenden, sähen die Leute „You have unsaved changes“ für etwas, das schon gespeichert ist, und das nächste Verwerfen würde es scheinbar rückgängig machen. Eine Seite, zwei Speichermodelle, und jedes Control macht klar, welches es nutzt.

> Live-Demo (Switch): Schalte New messages um, drück dann Simulate an error und schalte noch einmal: Der Switch schaltet sofort um, zeigt beim Speichern einen Spinner, springt dann zurück und zeigt den Grund. Probier sie unter [Switch](https://wingo-ui.com/components/switch) aus und installier sie mit `npx wingo-ui@latest add switch`.

Der Switch ist kostenlos. In einer `SettingsRow` bekommt der nackte Track auf Touchscreens eine Trefferfläche von 44px, das Label der Zeile ist mit ihm verbunden, sodass auch ein Tipp auf „Tägliche Zusammenfassung“ ihn umschaltet, und ein vertikales Wischen über den Track scrollt weiterhin die Seite.

## Wie baust du eine Danger Zone, die man nicht aus Versehen klickt?

Setz sie ans Ende, markier sie und mach den letzten Schritt bewusst. `SettingsSection` nimmt `danger` für einen roten Ring und Titel, `SettingsRow` nimmt `danger` für ein rotes Label, und der Bereich bekommt `danger: true`, damit ihn auch die Smartphone-Liste rot zeigt. Die Bestätigung ist ein [Alert Dialog](https://wingo-ui.com/components/alert-dialog) mit einem getippten Wort:

```tsx
// app/(app)/settings/danger-zone.tsx
"use client"

import { Trash2 } from "lucide-react"
import { useState } from "react"
import { AlertDialog } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { SettingsRow, SettingsSection } from "@/components/ui/settings-layout"

export function DangerZone() {
  const [open, setOpen] = useState(false)
  const [typed, setTyped] = useState("")

  return (
    <>
      <SettingsSection title="Danger Zone" description="Aktionen, die sich nicht rückgängig machen lassen." danger>
        <SettingsRow label="Konto löschen" description="Entfernt dein Profil und jeden Workspace, der dir gehört." danger>
          <Button variant="soft" tone="danger" size="sm" leftIcon={<Trash2 />} onClick={() => setOpen(true)}>
            Konto löschen
          </Button>
        </SettingsRow>
      </SettingsSection>

      <AlertDialog
        open={open}
        onOpenChange={(next) => {
          setOpen(next)
          if (!next) setTyped("")
        }}
        tone="danger"
        icon={<Trash2 />}
        title="Dein Konto löschen?"
        description="Dein Profil, deine Workspaces und Rechnungen werden endgültig gelöscht."
        confirmLabel="Konto löschen"
        cancelLabel="Abbrechen"
        confirmDisabled={typed !== "DELETE"}
        onConfirm={async () => {
          const res = await fetch("/api/account", { method: "DELETE" })
          if (!res.ok) throw new Error("Das Konto konnte nicht gelöscht werden. Versuch es noch mal.")
          window.location.assign("/goodbye")
        }}
      >
        <Input label="Tippe DELETE zur Bestätigung" value={typed} onValueChange={setTyped} autoComplete="off" spellCheck={false} />
      </AlertDialog>
    </>
  )
}
```

Beim Öffnen des Dialogs liegt der Fokus auf Abbrechen, ein Klick auf die abgedunkelte Seite tut nichts, und die Aktion bleibt gesperrt, bis das Wort stimmt. `onConfirm` gibt ein Promise zurück: Der Button dreht sich, solange die Anfrage läuft, und ein geworfener Fehler schüttelt die Karte und zeigt die Meldung über den Buttons, statt den Dialog zu schließen. Setz `typed` beim Schließen zurück, sonst ist der Dialog beim nächsten Öffnen schon scharf.

## Wie funktioniert die Einstellungsseite auf dem Smartphone?

Das Settings Layout misst seine eigene Breite und wechselt unter 768px die Navigation. Der Standardwert `mobileNav="list"` ist das iOS-Muster: Der Seitentitel wird zum großen Titel über einer gruppierten Liste, jeder Bereich ist eine Zeile mit Icon-Kachel und Beschreibung, und ein Tipp auf die Zeile schiebt den Bereich herein. Zurück geht es mit dem Zurück-Button, mit Escape oder mit einem Wischen nach rechts. Für kürzere Seiten gibt es zwei Alternativen: `"tabs"` (fixierte, scrollbare Pill-Tabs) und `"select"` (ein Select, das ein Bottom Sheet mit denselben Gruppen öffnet).

Auch die Save Bar ändert sich. Auf dem Smartphone ist sie immer unten fixiert, sitzt über der Safe Area und über einer Bottom Tab Bar, die ihre Höhe als `--bottom-bar-offset` veröffentlicht, hebt sich über die Bildschirmtastatur, und das Layout gibt dem Inhalt unten genug Abstand, damit die Bar nie die letzte Zeile verdeckt. Falls du schon mit `position: fixed` auf iOS gekämpft hast: Unser Artikel über [die Tastatur in iOS Safari und fixierte Leisten](https://wingo-ui.com/de/blog/ios-safari-keyboard-position-fixed) erklärt, warum der letzte Punkt Sorgfalt braucht, und der [Guide zur Bottom Navigation Bar](https://wingo-ui.com/de/blog/react-bottom-navigation-bar) behandelt die Tab-Bar-Seite von `--bottom-bar-offset`.

Auf dem Desktop zeigt der Speichern-Button einen Tastenhinweis für `saveShortcut` (Standard `mod+s`: Cmd+S auf dem Mac, sonst Ctrl+S), auf Touchscreens ausgeblendet. Der Shortcut ist nur aktiv, solange es etwas zu speichern gibt, auch in einem Textfeld. Den Rest der Zeit funktioniert also das „Seite speichern“ des Browsers weiter. Mit `saveShortcut=""` schaltest du ihn ab.

## Was, wenn du nur die Einstellungsliste fürs Smartphone brauchst?

Nimm die Komponente [List](https://wingo-ui.com/components/list), wenn ein ganzes Layout zu viel ist, etwa für einen Tab „Ich“ in einer Mobile-first-App mit ein paar Zeilen, die auf Seiten verlinken, einen aktuellen Wert zeigen oder etwas umschalten. Sie zeichnet das gruppierte iOS-Panel auch für sich allein:

```tsx
import { Bell, Globe, User } from "lucide-react"
import { List, type ListGroupData } from "@/components/ui/list"
import { Switch } from "@/components/ui/switch"

const groups: ListGroupData[] = [
  {
    id: "account",
    label: "Konto",
    items: [
      { id: "profile", title: "Profil", description: "emma.carter@northwind.app", icon: <User />, href: "/settings/profile" },
      { id: "notifications", title: "Benachrichtigungen", icon: <Bell />, trailing: <Switch defaultChecked aria-label="Benachrichtigungen" /> },
      { id: "language", title: "Sprache", icon: <Globe />, value: "Deutsch", href: "/settings/language" },
    ],
  },
]

export function MeTab() {
  return <List groups={groups} />
}
```

Zeilen mit `href` enden mit einem Chevron, `value` zeigt rechts die aktuelle Auswahl, und `trailing` nimmt jedes beliebige Control. Hat eine Zeile sowohl ein `href` als auch einen Switch in `trailing`, bekommt sie eine gestreckte klickbare Fläche statt eines umschließenden Buttons, damit der Switch nie in einem Link steckt. Der komplette Block Settings Page nutzt dieselbe List für die angemeldeten Geräte.

## Gibt es eine komplette Vorlage für eine React Settings Page?

Ja, wenn du lieber mit dem fertigen Screen anfängst. Der Block [Settings Page](https://wingo-ui.com/components/settings-page) baut auf dem Settings Layout auf und bringt sechs Bereiche mit: Profil (Foto-Upload mit Zuschnitt, Telefon, Zeitzone), Konto (E-Mail-Änderung mit 6-stelligem Code, Prüfung der Workspace-Adresse), Benachrichtigungen (ein Switch pro Ereignis und Kanal, Ruhezeiten, Zusammenfassung), Abrechnung (Plan, Nutzung, Rechnungen, die auf dem Smartphone zu Karten werden), Sicherheit (Passwort, eine Zwei-Faktor-Einrichtung in drei Schritten mit QR-Code und Backup-Codes, Sitzungen) und eine Danger Zone mit Export und getipptem Löschen. Seine Bereichs-IDs sind rumänische Schlüssel hinter englischen Labels: `profil`, `cont`, `notificari`, `facturare`, `securitate`, `pericol`.

```bash
npx wingo-ui@latest add settings-page
```

```tsx
"use client"

import { SettingsPage } from "@/components/blocks/settings-page"

export function SettingsRoute() {
  return (
    <SettingsPage
      sections={["profil", "notificari", "securitate", "pericol"]}
      onSave={async (section, values) => {
        const res = await fetch(`/api/settings/${section}`, { method: "PUT", body: JSON.stringify(values) })
        if (!res.ok) throw new Error("Speichern fehlgeschlagen. Bitte versuch es noch mal.")
      }}
      onPreferenceChange={async (preference, value) => {
        const res = await fetch("/api/settings/preferences", { method: "PATCH", body: JSON.stringify({ preference, value }) })
        if (!res.ok) throw new Error("Speichern fehlgeschlagen. Versuch es noch mal.")
      }}
      onDeleteAccount={async () => {
        const res = await fetch("/api/account", { method: "DELETE" })
        if (!res.ok) throw new Error("Das Konto konnte nicht gelöscht werden. Versuch es noch mal.")
        window.location.assign("/goodbye")
      }}
    />
  )
}
```

`onSave` deckt die Textfelder des offenen Bereichs ab und `onPreferenceChange` die Switches, die selbst speichern, mit demselben Rollback wie oben (hier lässt eine Ablehnung den Switch mit einem Toast zurückspringen). Beide Handler müssen bei einer gescheiterten Antwort werfen: `fetch` lehnt nur bei einem Netzwerkfehler ab, also zählt ein 500, den du nicht in einen Throw verwandelst, als erfolgreiches Speichern. Die Schlüssel der Einstellungen sind Pfade wie `notificari.quietHours` oder `securitate.loginAlerts`. Jeder Text ist eine `labels`-Prop und alle Daten haben eine eigene Prop (`profile`, `billing`, `sessions`), so ersetzt du die Beispieldaten Stück für Stück. Unser [Tutorial zum Next.js Admin Dashboard](https://wingo-ui.com/de/blog/nextjs-admin-dashboard-tutorial) verbindet diesen Block in einem kompletten Backoffice mit Server Actions, und die übrigen Artikel der [Kategorie Build](https://wingo-ui.com/de/blog/category/build) behandeln weitere komplette Screens.

> Live-Demo (Settings Page): Öffne Delete account und tippe DELETE, um den Button freizuschalten, oder drück Export und sieh zu, wie der Export vorher in einem Toast hochzählt. Probier sie unter [Settings Page](https://wingo-ui.com/components/settings-page) aus und installier sie mit `npx wingo-ui@latest add settings-page`.

## Wie geht es weiter?

Fang mit dem Layout und einem Bereich an, schließ dessen Speichern an und ergänze dann die Switches und die Danger Zone. [Switch](https://wingo-ui.com/components/switch) und [Input](https://wingo-ui.com/components/input) sind kostenlos, Settings Layout, List, Alert Dialog und der Block Settings Page gehören zu Wingo UI Pro ([Preise](https://wingo-ui.com/de/pricing)). Installier das Layout mit `npx wingo-ui@latest add settings-layout` und bau deinen ersten Bereich mit dem Code von oben.

## Komponenten in diesem Artikel

- [Settings Layout](https://wingo-ui.com/components/settings-layout) (Pro): A React settings page layout like macOS and iOS: a section nav, grouped label and control rows, and a save bar that slides up on changes. Installation: `npx wingo-ui@latest add settings-layout`
- [Settings Page](https://wingo-ui.com/components/settings-page) (Pro): A React account settings page for a back office: profile, account, notifications, billing, security and a danger zone, with a save bar. Installation: `npx wingo-ui@latest add settings-page`
- [Switch](https://wingo-ui.com/components/switch) (Free): A React toggle switch for settings that apply at once: a spring thumb that stretches and drags, thumb icons, and async saves that roll back. Installation: `npx wingo-ui@latest add switch`
- [List](https://wingo-ui.com/components/list) (Pro): A React list for settings, contacts, inboxes and orders: iOS grouped panels, selection, row menus, swipe actions and drag to reorder. Installation: `npx wingo-ui@latest add list`
- [Alert Dialog](https://wingo-ui.com/components/alert-dialog) (Pro): A React alert dialog for confirmations: an async confirm that loads, fails inline and shakes, plus an await confirm() API. Installation: `npx wingo-ui@latest add alert-dialog`
- [Input](https://wingo-ui.com/components/input) (Free): A React text input: icons and addons inside the box, a clear button, a loading spinner, a character counter and floating labels. Installation: `npx wingo-ui@latest add input`

## Häufige Fragen

### Soll eine Einstellungsseite automatisch speichern oder einen Speichern-Button haben?

Beides, jeweils für die passende Art von Control. Switches und andere einfache Umschalter speichern sofort und springen zurück, wenn die Anfrage scheitert. Textfelder sammeln sich in einem Entwurf, den eine Save Bar speichert, denn ein halb getippter Name soll nie beim Server ankommen.

### Wie warne ich vor ungespeicherten Änderungen auf einer Einstellungsseite?

Berechne ein Dirty-Flag, indem du den Entwurf mit den zuletzt gespeicherten Werten vergleichst, zeig eine Save Bar, solange es true ist, frag vor dem Wechsel des Bereichs nach und registrier einen beforeunload-Handler, damit auch das Schließen des Tabs warnt. Das Settings Layout erledigt alle drei Punkte über seine Props dirty, confirmLeave und warnBeforeUnload.

### Wie sollte eine Einstellungsseite auf dem Smartphone aussehen?

Mach aus der Navigation eine gruppierte Liste wie in den iOS-Einstellungen: Ein Tipp auf eine Zeile schiebt den Bereich mit Zurück-Button und Wischgeste herein. Pinn die Save Bar über die Tab Bar und die Safe Area, damit sie nie die letzte Zeile verdeckt oder hinter der Bildschirmtastatur verschwindet.

### Was gehört in die Danger Zone einer Einstellungsseite?

Aktionen, die sich nicht rückgängig machen lassen, etwa das Konto löschen, die Inhaberschaft übertragen oder einen Workspace entfernen. Setz den Bereich ans Ende, gib ihm einen roten Rahmen und Titel, nimm einen Button statt eines Switches und verlang eine getippte Bestätigung, bevor die Aktion läuft.

### Gibt es eine fertige Vorlage für eine React Settings Page?

Der Block Settings Page von Wingo UI bringt die Bereiche Profil, Konto, Benachrichtigungen, Abrechnung, Sicherheit und Danger Zone mit Save Bar und Smartphone-Layout mit und lässt sich mit npx wingo-ui@latest add settings-page installieren. Er gehört zu Wingo UI Pro; Switch und Input, die er nutzt, sind kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/react-settings-page
