A shared skeleton for settings pages: section nav with scroll-spy, titled sections of label | control rows, a sticky save bar that rises when there are unsaved changes, and a danger zone. Rows act like a Field, so kit controls are labelled and described without extra ids. Not bound to a form library — pass dirty from your form state.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { SettingsLayout, SettingsNav, SettingsSection, SettingsRow, SettingsSaveBar } from "@vamos-ui/react";
<SettingsLayout nav={<SettingsNav items={[{ id: "general", label: "General" }, { id: "danger", label: "Danger zone", tone: "danger" }]} />}>
<SettingsSection id="general" title="General" description="Basic information about the site.">
<SettingsRow label="Site name" description="Shown in the browser tab.">
<Input {...register("name")} />
</SettingsRow>
<SettingsRow label="Maintenance mode" layout="inline"><Switch /></SettingsRow>
</SettingsSection>
<SettingsSection id="danger" tone="danger" title="Danger zone">…</SettingsSection>
<SettingsSaveBar dirty={formState.isDirty} saving={formState.isSubmitting} onSave={handleSubmit(save)} onReset={() => reset()} />
</SettingsLayout>Examples
Save bar
The bar rises while dirty, saving or error, announces itself politely and saves with ⌘/Ctrl+S. Every third save here fails to show the error state.
Split sections
layout="split" puts the title and description in a start column on wide layouts and stacks them when narrow.
Danger zone
tone="danger" with a typed confirmation from useConfirm({ requireText }).
Loading and read-only
loading renders skeleton rows; readOnly disables every control through the row's field context — pair it with an Alert in notice.
API
SettingsLayout
| Prop | Type | Default |
|---|---|---|
navA SettingsNav: a start column on wide layouts, a scrolling strip below 672px of layout width. Rows stack when their card is under 32rem. | ReactNode | — |
width64rem or 80rem overall. | "md" | "lg" | "md" |
readOnlyDisables every row control. | boolean | false |
noticeBanner above the first section. | ReactNode | — |
stickyTopWhere the nav sticks. | number | string | "var(--topbar-h)" |
labelsOverrides for the settings namespace. | SettingsLayoutLabels | — |
SettingsNav
| Prop | Type | Default |
|---|---|---|
itemsAnchors (#id, scroll-spy) or route links (href). permission is checked with can(). | { id; label; href?; icon?; badge?; permission?; tone? }[] | — |
value · defaultValue · onValueChangeActive item. | string | — |
scrollSpyFollow the section in view. | boolean | true |
aria-labelName of the nav. | string | "Settings sections" |
SettingsSection
| Prop | Type | Default |
|---|---|---|
idAnchor for SettingsNav. | string | — |
title · descriptionHeading (<section aria-labelledby>) and help text. | ReactNode | — |
layoutTitle above the card, or in a start column. | "stacked" | "split" | "stacked" |
toneDanger hairline and title. | "default" | "danger" | "default" |
footerCard footer — a hint or a section button. | ReactNode | — |
loadingSkeleton rows (number = row count). | boolean | number | — |
headingLevelHeading level. | 2 | 3 | 2 |
SettingsRow
| Prop | Type | Default |
|---|---|---|
label · descriptionWired to the control (label for, aria-describedby). | ReactNode | — |
layoutSide by side (stacks when narrow), control at the end (switches), or full width. | "split" | "inline" | "stacked" | "split" |
htmlForControl id. Default: generated and passed to kit controls. | string | — |
badgeNext to the label. | ReactNode | — |
errorError text; marks kit controls invalid. | ReactNode | — |
disabledDisables kit controls in the row. | boolean | — |
SettingsSaveBar
| Prop | Type | Default |
|---|---|---|
dirtyUnsaved changes — shows the bar. | boolean | — |
savingSave button loading. | boolean | — |
onSave · onResetSave (also ⌘/Ctrl+S) and Discard. | () => void | — |
messageReplace "You have unsaved changes". | ReactNode | — |
errorSave failed: danger tone and an alert. | ReactNode | true | — |
blockNavigationbeforeunload warning while dirty. | boolean | true |
shortcuts⌘/Ctrl+S saves. | boolean | true |
saveLabelSave button text. | string | "Save changes" |