Patterns · Layout
SettingsSection
Titled card of settings rows with a save footer and dirty state, loading and no-access states, and a danger-zone variant.
Profile settings
Profile
How you appear to your teammates.
Shown on comments and assignments.
import { Button, Input, SettingsRow, SettingsSection, Switch } from '@rookcom/monarch-react';
<SettingsSection
title="Profile"
description="How you appear to your teammates."
actions={<Button variant="primary">Save</Button>}
>
<SettingsRow
label="Display name"
description="Shown on comments and assignments."
required
>
<Input defaultValue="Ada Lovelace" />
</SettingsRow>
<SettingsRow label="Show my status">
<Switch defaultChecked />
</SettingsRow>
</SettingsSection>Usage examples
Save footer with dirty state
import { useState } from 'react';
import { Input, SettingsRow, SettingsSection, Switch } from '@rookcom/monarch-react';
function DirtyDemo() {
const [saved, setSaved] = useState({ name: 'Northfield', digest: true });
const [draft, setDraft] = useState(saved);
const [saving, setSaving] = useState(false);
const dirty = draft.name !== saved.name || draft.digest !== saved.digest;
return (
<SettingsSection
title="Workspace"
description="Name and notifications for everyone in this workspace."
dirty={dirty}
saving={saving}
onCancel={() => setDraft(saved)}
onSave={() => {
setSaving(true);
setTimeout(() => {
setSaved(draft);
setSaving(false);
}, 800);
}}
>
<SettingsRow label="Workspace name">
<Input value={draft.name} onChange={(e) => setDraft({ ...draft, name: e.target.value })} />
</SettingsRow>
<SettingsRow label="Weekly digest" description="A summary email every Monday.">
<Switch
checked={draft.digest}
onCheckedChange={(digest) => setDraft({ ...draft, digest })}
/>
</SettingsRow>
</SettingsSection>
);
}
<DirtyDemo />Danger zone
import { Button, SettingsActionRow, SettingsSection } from '@rookcom/monarch-react';
<SettingsSection
title="Danger zone"
description="These actions affect everyone in the workspace."
tone="danger"
>
<SettingsActionRow
title="Transfer ownership"
description="Make another admin the owner. You stay an admin."
action={<Button>Transfer</Button>}
/>
<SettingsActionRow
title="Delete workspace"
description="Removes every project, file and member. This can’t be undone."
action={<Button variant="danger">Delete workspace</Button>}
/>
</SettingsSection>Loading and no access
import { SettingsSection } from '@rookcom/monarch-react';
<div style={{ display: 'grid', gap: '1rem', width: '100%' }}>
<SettingsSection title="Payroll" state="loading">
{null}
</SettingsSection>
<SettingsSection
title="Security"
state="no-access"
noAccessDescription="Ask a workspace admin to change security settings."
>
{null}
</SettingsSection>
</div>Purpose
Groups related settings under a heading and description, in a card. SettingsRow lays out one setting and wires its label, description and error to the control; SettingsActionRow lays out one action such as "Delete workspace". The footer can be the standard Save/Discard bar driven by a dirty flag.
Use when
- Settings and preferences pages.
- A form section that saves as a unit and should say when it has unsaved changes.
- A destructive zone (delete, transfer) with
tone="danger"and SettingsActionRow.
Avoid when
- Data-entry forms that are not settings: use Field directly.
- Settings that apply immediately (a single switch list): use SwitchSettingsList.
Design principles
- Label and help on one side, control on the other, so long settings pages scan quickly.
- Each row is a Field, so accessibility wiring is automatic.
- Save/Discard live in the section footer, not per row, and stay disabled until something changed.
- The product owns the draft and the save; the section only shows the state it is given.
API reference
import { SettingsActionRow, SettingsRow, SettingsSection } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SettingsActionRow | component | client | .mn-settings-section |
SettingsRow | component | client | .mn-settings-section |
SettingsSection | component | client | .mn-settings-section |
SettingsSection renders a <section> labelled by its heading; native attributes pass through.
SettingsRow is a Field: the control child receives the label, description, required, disabled and invalid wiring. invalid defaults to true when error is given.
With onSave, the footer is a polite status line, any actions, Discard (onCancel) and Save. Without it, the footer is actions alone, as before.
SettingsActionRow renders a row with a title, description and an action node; it is not a form field.
Generated from the TypeScript declarations of packages/react/src/patterns/layout/settings-section.
Properties
SettingsActionRow
One action in a settings card: title and consequence on the left, a button on the right. Used for danger-zone items (transfer, archive, delete) where the row is not a form control.
| Prop | Type | Description |
|---|---|---|
action required | ReactNode | The button that starts the action, usually opening a ConfirmDialog. |
title required | ReactNode | What the action does ("Delete workspace"). |
children | ReactNode | |
className | string | |
description | ReactNode | The consequence, in one or two sentences. |
id | string |
SettingsRow
One setting: label and description on the left, control on the right.
| Prop | Type | Description |
|---|---|---|
children required | ReactNode | The control: Input, Select, Switch, Combobox. It receives the Field ids. |
label required | ReactNode | Label of the setting; becomes the control's label. |
className | string | |
description | ReactNode | |
disabled | boolean | |
error | ReactNode | Error message rendered under the control. |
id | string | Pass `id` to match an existing control id. |
invalid | boolean | |
required | boolean |
SettingsSection
Titled group of settings in a card, as in Workspace App Settings.
| Prop | Type | Description |
|---|---|---|
title required | ReactNode | Section title (h2). |
actions | ReactNode | Footer with Save/Cancel actions. |
cancelLabel | string | Default "Discard". |
children | ReactNode | |
className | string | |
description | ReactNode | |
dirty | boolean | The draft differs from the saved settings. Shows `dirtyMessage` in the footer (a polite status) and enables Save and Discard. Exposed as `data-dirty`. |
dirtyMessage | ReactNode | Status text while `dirty`. Default "You have unsaved changes." |
headingLevel | "h2" | "h3" | Heading level if the section is nested under another h2. |
id | string | |
loadingLabel | string | Announced while loading. Default "Loading settings". |
noAccessDescription | ReactNode | Explanation under the `no-access` title, such as who can change them. |
noAccessTitle | ReactNode | Title of the `no-access` notice. Default "You don’t have access to these settings". |
onCancel | (() => void) | Discards the draft. Shown when `onSave` is given. |
onSave | (() => void) | Renders the standard footer: a status line, Discard and Save. The product owns the draft; Save calls this, Discard calls `onCancel`. Save and Discard are disabled while the section is not `dirty`. |
saveLabel | string | Default "Save changes". |
saving | boolean | Save is in flight: Save shows a spinner and Discard is disabled. |
state | "loading" | "ready" | "no-access" | `loading` shows a skeleton instead of the rows (aria-busy); `no-access` replaces the rows and footer with a locked notice. Default `ready`. Exposed as `data-state`. |
stickyFooter | boolean | Keep the footer stuck to the bottom of the scroll area while `dirty`. |
tone | "danger" | "default" | `danger` for destructive zones (delete workspace, transfer ownership). |
Variants
| Prop | Values | Description |
|---|---|---|
tone | 'default''danger' | danger uses danger border and title colours. Exposed as data-tone. |
headingLevel | 'h2''h3' | Heading element for the title. Default h2. |
stickyFooter | 'false''true' | While dirty, the footer sticks to the bottom of the scroll area. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Dirty | data-dirty | Enables Save and Discard, tints the footer and puts dirtyMessage in the status line. |
| Saving | aria-busy | Save shows a spinner and ignores clicks; Discard is disabled. |
| Loading | data-state="loading" | A skeleton replaces the rows; the footer is hidden; the section is busy. |
| No access | data-state="no-access" | A locked notice replaces the rows and the footer. |
| Row error | aria-invalid on the control | error renders a polite live message under the control and marks it invalid. |
| Row required / disabled | – | Passed to the control through Field. |
Interaction behaviour
- Save calls
onSave; Discard callsonCancel. Both are disabled while the section is not dirty. - The status line announces "You have unsaved changes." when the section becomes dirty and clears after saving.
- Rows have no behaviour of their own beyond Field wiring; controls behave as usual.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Moves through the row controls, then Discard and Save. |
| Enter / Space | On Save or Discard, activates it. |
Disabled Save and Discard are skipped by Tab until the section is dirty.
Accessibility
- Role
- region (section with aria-labelledby)
- Accessible name
- The section title.
- Each row label is the control’s accessible name; the description is its accessible description.
- Row errors are announced through
aria-live="polite". - The dirty message is a
role="status"line, so it is announced without moving focus. - Loading and saving set
aria-busyon the section.
Responsive behaviour
- From 1024px the section header sits beside the card (18rem column); below, it stacks above.
- From 768px each row puts its text and control side by side (control column up to 20rem); below, they stack.
- The footer wraps: the status line takes its own line on narrow screens and the buttons stay together.
Theme support
- Card, borders, footer, dirty tint and danger colours use Monarch semantic tokens, so every theme applies.
Do and don't
Do
- Pass exactly one control per SettingsRow.
- Derive
dirtyfrom the draft and the saved value, so Discard and Save stay honest. - Use
tone="danger"with SettingsActionRow for irreversible actions, each behind a ConfirmDialog.
Don't
- Do not wrap the control in another Field.
- Do not mix destructive and ordinary settings in one section.
- Do not add a second Save button inside the rows.
Related components
- Field: The wiring each row uses.
- SwitchSettingsList: Lists of switches that apply immediately.
- SectionCard: A titled card for content that is not settings.
- ConfirmDialog: Confirm the actions in a danger zone.
- PageHeader: Title of the settings page.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | Button, EmptyState, Field, LoadingState |
Testing coverage
- Section is a region named by its title; rows wire label, description and required to the control; an error marks it invalid.
- Danger tone is exposed as data-tone.
- The save footer stays disabled until dirty, announces unsaved changes, discards and saves (keyboard).
- Saving shows a busy Save and blocks Discard; custom labels, sticky footer and extra actions.
- Loading and no-access states replace the rows and the footer.
- SettingsActionRow pairs a consequence with its button.
- Renders on the server.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/settings-section; Cross-browser axe: /components/settings-section |
Tests: packages/react/src/patterns/layout/settings-section