Files
openclaw/ui/docs/design-system/settings-design.md
Peter Steinberger 0a8226c3db fix(ui): give embedded settings sections the shared section rhythm (#126541)
* fix(ui): give embedded settings sections the shared section rhythm

The agents tab panel hosted settings sections in a bare div, so sections
stacked with zero separation; the bespoke .agents-main margin rule in
agents.css missed the nested tabpanel entirely. Descriptions also pulled
up to 4px under control-height header actions (squeezed Verify/Save rows).

Add a .settings-stack primitive to settings.css for embedded surfaces,
use it on the agent tab panel, delete the page-local margin fork, and let
section descriptions clear action-bearing headers.

* test(agents): export getRuntimeConfigSourceSnapshot from runtime-snapshot mock

Main's checks-node-compact-large shard is red: #126531 routed
provider-model-routes through projectConfigOntoRuntimeSourceSnapshot,
which reads getRuntimeConfigSourceSnapshot, and this suite's explicit
vi.mock factory did not export it (24 failures). Return null so the
projection no-ops and resolvers keep reading the provided config.
2026-08-19 22:31:35 -07:00

60 lines
3.6 KiB
Markdown

# Settings Design Language
Every settings surface (the `/settings` takeover pages plus the Plugins/Skills hubs) uses one structural pattern. Workspace styles live in `ui/src/styles/settings.css`, while controls shared with startup surfaces live in `ui/src/styles/settings-controls.css`; templates are built through the helpers in `ui/src/components/settings-ui.ts`.
## Anatomy
```
.settings-page ← single column, 760px (or --wide: 1120px)
.settings-section ← repeated per topic
.settings-section__heading ← plain uppercase text label, outside any surface
.settings-group ← the ONLY surface (card bg, border, radius-lg)
.settings-row ← title/description left, one control right
.settings-row ← hairline divider between rows
```
- **Sections are typography, not chrome.** Grouping comes from whitespace + a small uppercase heading — never a card header.
- **Embedded surfaces keep the rhythm.** A container that hosts sections without `.settings-page`'s centered column (tab panels, split layouts) takes `.settings-stack`; never re-space sections with page-local margin rules.
- **Exactly one level of elevation.** A group never contains another card, callout, or bordered box. Nested detail uses `.settings-subrows` (indented rows), a stacked row, or a drill-in nav row.
- **Row anatomy:** left is title (`--control-ui-text-md`, weight 500) over an optional one-line description (muted, sm). Right is exactly one control: toggle, select, segmented, button, plain value, or chevron (nav). Wide editors use the `stacked` variant.
- **Lists are rows too.** An entity list (plugin, device, session) is a group whose rows carry an action cluster in the control slot — same anatomy as a toggle row.
## Rules
- **No status pills.** Status is `renderSettingsStatus` — a dot + plain text (`● Connected`). Badges (`.settings-count`) exist only for genuine counts.
- **Spacing uses `--space-*` tokens** (`base.css`); no hardcoded paddings/gaps.
- **Motion budget:** color/background transitions only. No enter animations, staggered reveals, or hover glows.
- **Buttons:** default `.btn` (quiet). `--accent` primary at most once per view. Danger actions live in a `danger: true` section at the page bottom.
- **One control set.** Use `renderSettingsToggleRow` (preferred: label-wrapped, whole row clickable, accessible name for free) or `renderSettingsToggle` with a required `ariaLabel`, `renderSettingsSegmented`, `.settings-select`, `.settings-input`. Do not add another toggle or badge variant.
- **Every control needs an accessible name.** Row titles are plain text, not `<label>`s — selects/inputs in a control slot must carry `aria-label` (usually the row title string).
- **No new page CSS files for settings surfaces.** Page-specific styles belong in `settings.css` only when a primitive is genuinely missing — extend the system, don't fork it.
## Helpers
```ts
import {
renderSettingsPage,
renderSettingsSection,
renderSettingsRow,
renderSettingsNavRow,
renderSettingsToggleRow,
renderSettingsSegmented,
renderSettingsStatus,
renderSettingsValue,
renderSettingsEmpty,
} from "../../components/settings-ui.ts";
renderSettingsPage([
renderSettingsSection({ title: t("settings.notifications") }, [
renderSettingsToggleRow({
title: t("settings.systemNotifications"),
description: t("settings.systemNotificationsDesc"),
checked,
onChange,
}),
]),
]);
```
Custom content inside a group (tables, meters) is allowed as an escape hatch — keep it inside one `.settings-group` and match row paddings (`--space-3 --space-4`).