Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

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:

src/pages/PickupSettings.tsx

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

PropTypeDefaultDescription
title

required

stringThe page's title, its h1.
breadcrumbsReactNodeThe trail of pages above it, a Breadcrumbs, such as Settings for a page under it.
badgeReactNodeA ScopeBadge after the title, saying what the page's settings apply to.
descriptionReactNodeOne line under the title saying what the page is for.
actionsReactNodeThe 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.
asideReactNodeA 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.
asideLabelstring"Details"Names the right rail's landmark, such as "Customer details". Defaults to "Details".
fillbooleanfalseGives 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.
footerReactNodeA 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.

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

PropTypeDefaultDescription
title

required

stringThe section's heading, an h2 in title-md, which names it.
descriptionReactNodeOne line under the heading saying what the section is for.
actionReactNodeThe 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.
dividerbooleanfalseDraws a hairline under the heading's row, for a section of SettingRows. Defaults to false.
loadingbooleanfalseShows a loading pill in place of the content while the section's data loads, the rest of the page staying ready. Defaults to false.
loadingLabelstring"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.
dirtybooleanfalseWhether 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.
savingbooleanfalseShows 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.
saveLabelstring"Save"Save's label. Defaults to "Save".
cancelLabelstring"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.

KeysWhat they do
EnterSaves, 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.

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

PropTypeDefaultDescription
title

required

stringWhat the setting is, an h3 in title-sm.
stateReactNodeIts current state under the title, such as "Screening every renewal.", in body-sm and text-secondary.
actionReactNodeThe 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.

See it in the roastery demo's settings.

  • 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.