Source: https://monarchui.dev/components/switch-settings-list/

Components · Forms

# SwitchSettingsList

On/off settings that each save on their own, with saving, error and retry per row.

@rookcom/monarch-react

Component

## Notification preferences

## Usage examples

### Failed save, rollback and retry

## Purpose

Shows a group of on/off preferences, such as notification or privacy settings, where each switch saves immediately. Every row has its own saving, saved and failed state, so one failure never hides behind a page-level toast.

### Use when

- Each toggle is persisted on its own (a PATCH per preference) rather than with a form Save button.
- Saves can fail and the user must see which setting did not stick.

### Avoid when

- Switches are submitted together with other fields: use Field with `layout="row"` and a Switch inside a form.
- A single setting with no group: a Switch with `label` and `description`, or SettingsRow.

## Design principles

- Optimistic: the switch moves at once and rolls back only if the save fails.
- Status lives on the row it belongs to: a spinner while saving, a brief "Saved", or the error with Retry.
- Results are announced, not only shown, so screen-reader users hear "Saved" or the error.
- No data fetching: the product passes `checked` and an `onCheckedChange` that returns its save promise.

## API reference

```tsx
import { SwitchSettingsItem, SwitchSettingsList } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `SwitchSettingsItem` | component | client | `.mn-switch-settings-` |
| `SwitchSettingsList` | component | client | `.mn-switch-settings-` |

`SwitchSettingsList` renders a `<div>` (role `group` when it has a title or label) with an optional heading and a `<ul>` of rows; the ref goes to the `<div>`.

`SwitchSettingsItem` renders an `<li>` (ref) with the label, description, status and a Monarch Switch.

Status wording (`labels`: saving, saved, error, retry), `savedDuration` and `getErrorMessage` are set on the list and can be overridden per row.

Generated from the TypeScript declarations of `packages/react/src/components/forms/switch-settings-list`.

## Properties

### `SwitchSettingsItem`

One setting row: label and description on the left, the switch and its save status on the right.

| Prop | Type | Description |
| --- | --- | --- |
| `label` required | `ReactNode` | The setting's name. Labels the switch. |
| `checked` | `boolean` | The saved state (controlled). |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `defaultChecked` | `boolean` | The initial saved state (uncontrolled). |
| `description` | `ReactNode` | What the setting does. Describes the switch. |
| `disabled` | `boolean` |  |
| `id` | `string` |  |
| `labels` | `SwitchSettingsLabels` | Override the list's status wording for this row. |
| `onCheckedChange` | `((checked: boolean) => void \| Promise<void>)` | Saves the new state. The switch moves at once; return a promise to show "Saving" and then "Saved", or reject it to roll the switch back and show the error with a retry button. |
| `onStatusChange` | `((status: SwitchSettingsStatus) => void)` | Called whenever the row's save status changes. |

### `SwitchSettingsList`

A group of on/off settings that each save on their own, such as notification preferences. Rows toggle optimistically, show their own saving and error state with retry, and roll back if the save fails.

| Prop | Type | Description |
| --- | --- | --- |
| `children` required | `ReactNode` | SwitchSettingsItem rows. |
| `className` | `string` |  |
| `description` | `ReactNode` | A sentence under the title. |
| `getErrorMessage` | `((error: unknown) => ReactNode)` | Turns a failed save into a message shown after `labels.error`. Default: the Error's message. |
| `headingLevel` | `"h2" \| "h3" \| "h4" \| "h5" \| "h6"` | Heading element for `title`. Default `h3`. |
| `id` | `string` |  |
| `labels` | `SwitchSettingsLabels` | Status wording for every row (rows can override). |
| `savedDuration` | `number` | How long "Saved" stays visible, in ms. Default 2000. |
| `title` | `ReactNode` | Group heading, for example "Email notifications". Names the group. |

## Variants

One visual form: a bordered list of rows. Wording is configurable through `labels` for translation.

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Idle | `data-status="idle"` | The switch shows the saved value. |
| Saving | `data-status="saving", aria-busy="true" on the switch` | The switch already shows the new value; a spinner named "Saving" sits beside it and further toggles are ignored until the save settles. |
| Saved | `data-status="saved"` | A check and "Saved" for `savedDuration` (2 s), announced politely. Only when `onCheckedChange` returned a promise. |
| Error | `data-status="error"` | The switch rolls back, the error shows under the label and is announced, and a Retry button repeats the failed value. |
| Disabled | `data-disabled, disabled on the switch` | Disabled text colour; the switch cannot be toggled. |

## Interaction behaviour

- Toggling calls `onCheckedChange(next)` with the switch already moved.
- Resolved promise: "Saved" appears and clears. Rejected: the switch returns to its previous value and the row shows the error and Retry.
- Retry calls `onCheckedChange` again with the value that failed. Toggling the switch again also retries.
- Uncontrolled rows (`defaultChecked`) keep the new value after a successful save. Controlled rows (`checked`) show the optimistic value until the parent’s `checked` changes, and always follow a new `checked` when nothing is saving.
- `onStatusChange(status)` reports each row’s status, for example to block navigation while saving.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Moves between switches, and to a row’s Retry button when shown. |
| Space / Enter | Toggles the focused switch; activates Retry. |

## Accessibility

Role

group (list), switch (each row)

Accessible name

The list is named by

title

(or

aria-label

); each switch by its row

label

.

- The row description is the switch’s accessible description; while a save has failed, the error is added to it.
- Each row has a polite `role="status"` region that announces "Saved" or the error. The saving spinner is a named status.
- Retry is named "Retry {label}", so several failed rows stay distinguishable.
- Rows are list items in a `<ul>`, so the number of settings is announced.

## Responsive behaviour

- Label and description wrap; the switch and status stay on the right.
- Below 480px a row may wrap so a long status or the Retry button moves under the text, with no horizontal scrolling at 320px.

## Theme support

- Surface, borders and text are semantic tokens, so light, dark and more-contrast themes apply automatically.
- "Saved" uses `--mn-color-success-strong` and errors `--mn-color-danger-strong`, both readable on the surface in every theme; the switch track follows the product accent.
- The "Saved" fade uses the duration tokens, which are zero under reduced motion.

## Do and don't

### Do

- Return the save promise from `onCheckedChange`; that is what drives the states.
- Reject with an Error whose message tells the user what to do.
- Group related settings under a `title`, one list per group.

### Don't

- Do not also show a toast for each save; the row already reports it.
- Do not use it inside a form that has its own Save button.
- Do not resolve the promise when the save failed.

## Related components

- [Switch](https://monarchui.dev/components/switch/): The control used in each row.
- [SettingsSection](https://monarchui.dev/components/settings-section/): Card of mixed settings with one Save footer.
- [Field](https://monarchui.dev/components/form-field/): `layout="row"` for a single switch row inside a form.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Spinner](https://monarchui.dev/components/spinner/), [Switch](https://monarchui.dev/components/switch/) |

## Testing coverage

- A titled, described group of list items; switches are labelled and described by their rows.
- Uncontrolled: Space toggles optimistically, aria-busy and a "Saving" status while pending, toggles during a save are ignored, "Saved" is announced and clears after `savedDuration`.
- Failure: rolls back, the error describes the switch and is announced, "Retry {label}" repeats the failed value, and `onStatusChange` reports saving → error → saving → saved.
- Controlled: keeps the optimistic value until the parent updates, then follows the parent.
- A synchronous handler toggles without a "Saved" state; disabled rows do not toggle; `aria-label` names the group.
- Labels and error messages can be set per list and per row.
- Renders on the server.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/switch-settings-list |

Tests: `packages/react/src/components/forms/switch-settings-list`

## All example code

### Notification preferences

```tsx
import { useState } from 'react';
import { SwitchSettingsItem, SwitchSettingsList } from '@rookcom/monarch-react';

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
}

function NotificationsDemo() {
  const [prefs, setPrefs] = useState({ alerts: true, devices: true, digest: false });
  const save = (key: keyof typeof prefs) => async (next: boolean) => {
    await wait(700);
    setPrefs((p) => ({ ...p, [key]: next }));
  };
  return (
    <div style={{ width: '100%', maxWidth: 560 }}>
      <SwitchSettingsList
        title="Security notifications"
        description="Emails about activity on your account."
      >
        <SwitchSettingsItem
          label="Security alerts"
          description="Password changes and new recovery methods."
          checked={prefs.alerts}
          onCheckedChange={save('alerts')}
        />
        <SwitchSettingsItem
          label="New device sign-in"
          description="When your account is used on a device for the first time."
          checked={prefs.devices}
          onCheckedChange={save('devices')}
        />
        <SwitchSettingsItem
          label="Weekly summary"
          checked={prefs.digest}
          onCheckedChange={save('digest')}
        />
      </SwitchSettingsList>
    </div>
  );
}

<NotificationsDemo />
```

### Failed save, rollback and retry

```tsx
import { SwitchSettingsItem, SwitchSettingsList } from '@rookcom/monarch-react';

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
}

function FailingDemo() {
  return (
    <div style={{ width: '100%', maxWidth: 560 }}>
      <SwitchSettingsList title="Delivery">
        <SwitchSettingsItem
          label="In-app notifications"
          description="This example always fails, to show rollback and retry."
          defaultChecked
          onCheckedChange={async () => {
            await wait(700);
            throw new Error('Check your connection and try again.');
          }}
        />
      </SwitchSettingsList>
    </div>
  );
}

<FailingDemo />
```
