Settings pages
A settings page made of sections, each with a heading, one line about it and its one action.
A settings page is a detail page for one area of settings, such as pickup or the team, made of sections that each hold one kind of setting. Build it from SettingsPage, SettingsSection and SettingRow, and every settings page reads the same.
The parts
The page
SettingsPage: breadcrumbs back to the settings index, the title with an optional scope badge, one line about the page, then its sections, at most 960px wide.Sections
SettingsSection: a heading, one line about it and its one action, then its content. A section can load on its own while the rest of the page is ready, or be a form whose Save waits for a change.Rows
SettingRow: one setting in a section, with what it is, its current state under it, and its one action at the end, primary only when it turns something on.
A page for pickup, with one section:
import type { ReactElement } from "react";
import {
Breadcrumbs,
SettingsPage,
SettingsSection,
Switch,
} from "@butteryui/react";
export function PickupSettings(): ReactElement {
return (
<SettingsPage
title="Pickup"
breadcrumbs={
<Breadcrumbs items={[{ label: "Settings", href: "/settings" }]} />
}
description="When and where customers collect their orders."
>
<SettingsSection
title="Pickup"
description="Let customers collect at the roastery."
>
<Switch label="Offer pickup" defaultChecked />
</SettingsSection>
</SettingsPage>
);
}SettingsPage
When to use it
Use it for
- A page of settings under a settings index, such as pickup, the team or notifications
- A dense form or a preferences matrix, with width="narrow"
Not for
- The index of every settings page: use SettingsIndex on an AppPage
- A page of records, such as orders: use AppPage with a DataTable
Examples
Notification preferences
A narrow settings page whose one row is turned on or off by its action.
Notifications
What the roastery emails you about.
Stock
Green coffee running low
Off.
Pickup settings
A settings page in the page grammar, its one section a form that saves a change.
Pickup
Pickup hours: Weekdays 8:00 to 16:00.
Hours
When the counter hands out orders.
Variants and states
A settings page
Breadcrumbs, the title with its scope, one line about it, then its sections.
Fraud checks
Which payments are checked before they're accepted.
Subscriptions
Screen renewals
Screening every renewal.
Props
SettingsPage
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | The page's title, its h1. | |
breadcrumbs | ReactNode | The trail of pages above it, a Breadcrumbs, such as Settings for a page under it. | |
badge | ReactNode | A ScopeBadge after the title, saying what the page's settings apply to. | |
description | ReactNode | One line under the title saying what the page is for. | |
actions | ReactNode | The page's actions at the end of the title's row: PageHeader.Actions. | |
width | "medium" | "narrow" | "medium" | "medium" (960px, the default) or "narrow" (820px), for a dense form or a preferences matrix. |
aside | ReactNode | A right rail beside the page, 320px wide from 1040px of the shell's column beside a full sidebar (1280 of shell, with no side panel docked), under the page otherwise, including beside a narrow shell's icon rail. | |
asideLabel | string | "Details" | Names the right rail's landmark, such as "Customer details". Defaults to "Details". |
fill | boolean | false | Gives the page's last block the rest of the height, so a table scrolls inside it with its header
sticky while the header and filters stay put; the table's pagination then keeps floating lanes above
it. Defaults to false. |
footer | ReactNode | A small footer that ends the page under a hairline, such as a feedback link and the keyboard
shortcuts, in body-sm and text-secondary. |
It also takes Omit<ComponentPropsWithRef<"main">, "title">.
Accessibility
The page's main landmark. Breadcrumbs come first as their own navigation; the title is the h1, each section a region named by its h2, and the footer a footer element after the sections.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
An AppPage at most 960px wide (820 with width="narrow"), centered, with 16, 24 or 32px of side padding as its column widens: the header first, its sections 24 under it and 32 apart, then a footer under a hairline. A long title truncates once the actions have folded into "…" and shows in full on hover; its hit areas are the breadcrumbs' and actions' own.
Related
SettingsSection
When to use it
Use it for
- Each group of settings on a SettingsPage
- A form on a settings page: Save stays disabled until something changes
- A section whose data arrives separately from the rest of the page
Not for
- Wrapping settings in a card: sections aren't cards
- A page section that isn't a setting: use AppPage.Section
- One setting with its state and an action: use SettingRow inside the section
Examples
Passkeys
A section with a heading, one line about it, its one action at the end of the heading's row, and its content.
Passkeys
Sign in with your fingerprint or a security key.
No passkeys yet.
Pickup address form
A settings form whose Save and Cancel wait for a change, then save it.
Pickup address
Where customers collect their orders.
Saved: 12 Mill Street, Portland
Sessions that load
A section that loads on its own while the rest of the page is ready.
Sessions
Where you're signed in.
Variants and states
With one action
Its action sits at the end of the heading's row.
Passkeys
Sign in with your fingerprint or a security key.
No passkeys yet.
A form
Save and Cancel stay disabled until something changes.
Pickup address
Where customers collect their orders.
Loading on its own
A pill in the section's place while its data arrives.
Sessions
Where you're signed in.
Props
SettingsSection
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | The section's heading, an h2 in title-md, which names it. | |
description | ReactNode | One line under the heading saying what the section is for. | |
action | ReactNode | The section's one action at the end of the heading's row, such as an Edit button. A form's Save and Cancel take its place. | |
divider | boolean | false | Draws a hairline under the heading's row, for a section of SettingRows. Defaults to false. |
loading | boolean | false | Shows a loading pill in place of the content while the section's data loads, the rest of the page staying ready. Defaults to false. |
loadingLabel | string | "Loading" | What the loading pill says. Defaults to "Loading". |
onSave | (() => void) | Makes the content a form, with Save (primary) and Cancel that stay disabled until dirty. Called on
Save, and on Enter in a field while there's a change. | |
dirty | boolean | false | Whether anything in the form has changed, which enables Save and Cancel. Defaults to false. |
onCancel | (() => void) | Called on Cancel, to put the form's values back. Without it there's no Cancel. | |
saving | boolean | false | Shows Save as busy while the change is saved. Defaults to false. |
buttons | "header" | "footer" | "header" | Where Save and Cancel go: "header" (the default), at the end of the heading's row, or "footer", under the content. Never both. |
saveLabel | string | "Save" | Save's label. Defaults to "Save". |
cancelLabel | string | "Cancel" | Cancel's label. Defaults to "Cancel". |
It also takes Omit<MotionElementProps<"section">, "title" | "onSubmit">.
Accessibility
A region named by its h2, its description a paragraph under it. While loading it's marked busy and its loading pill isn't read out. As a form, its content is a form element: Save is its submit button, Enter in a field saves while there's a change, and Save and Cancel stay focusable but disabled (aria-disabled) until something changes.
| Keys | What they do |
|---|---|
| Enter | Saves, from a text field in a form section, while something has changed |
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It fills its column. The heading's row runs the full width: the h2 truncates and shows in full on hover, the description wraps under it, and the action, or Save and Cancel, keeps its size at the row's end. The content starts 16px under the row and a loading pill takes a 64px-tall place while it loads; Save and Cancel are 28px tall, and the only hit areas are the buttons' own.
Related
See it in the roastery demo's settings.
SettingRow
When to use it
Use it for
- A setting whose change happens elsewhere, in a dialog or another page, opened by its action
- A list of such settings in a SettingsSection with divider
Not for
- A field edited in place: put the field in a SettingsSection form
- A grid of on and off choices across channels: use PreferencesMatrix
Examples
Fraud checks
Setting rows under a hairline, the one that turns something on in primary until it's on.
Fraud checks
Screen subscription renewals
Screening every renewal.
Check saved cards
Off. Cards saved for later payments aren't checked.
Payout schedule
A setting changed by its row's action, its current state updating under the title.
Payout schedule
Every Friday
Variants and states
In a section
Rows under a hairline; primary only for the one that turns something on.
Fraud checks
Screen subscription renewals
Screening every renewal.
Check saved cards
Off. Cards saved for later payments aren't checked.
Props
SettingRow
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | What the setting is, an h3 in title-sm. | |
state | ReactNode | Its current state under the title, such as "Screening every renewal.", in body-sm and text-secondary. | |
action | ReactNode | The row's one action at its end, such as a Manage button; primary only when it turns something on. |
It also takes Omit<ComponentPropsWithRef<"div">, "title">.
Accessibility
Its title is an h3 under the section's h2; its state is a paragraph. The action is whatever control it's given, named on its own ("Manage screening", not just "Manage", when several rows have one).
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It fills its parent's width: the title and state take the room left, wrapping rather than truncating, and the action keeps its own size 16px from them at the end, centered on the text. Its height is its text's, and its only hit area is the action's own.
Related
See it in the roastery demo's settings.
Related
- SettingsIndex
A settings home page: titled sections of links, each with an icon, a title and a line on what's inside, in up to three columns.
- Page header
The top of an app page, with its title, an optional value and the page's actions.