Components · Forms
SwitchSettingsList
On/off settings that each save on their own, with saving, error and retry per row.
Notification preferences
Security notifications
Emails about activity on your account.
Password changes and new recovery methods.
When your account is used on a device for the first time.
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 />Usage examples
Failed save, rollback and retry
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 />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
labelanddescription, 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
checkedand anonCheckedChangethat returns its save promise.
API reference
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
onCheckedChangeagain 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’scheckedchanges, and always follow a newcheckedwhen 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(oraria-label); each switch by its rowlabel.
- 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-strongand 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: The control used in each row.
- SettingsSection: Card of mixed settings with one Save footer.
- Field:
layout="row"for a single switch row inside a form.
Dependencies
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
onStatusChangereports 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-labelnames 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