Settings page

The grouped settings menu, shipped acrm !4018 (feat(nastaveni): seskupení podle Attio, merged 83d87bb24). Every item of the settings menu is its own page, not a tab: SectionTopBar (identity = the menu item's own icon) plus one centred content column. apps/web/src/pages/settings/SettingsLayout.tsx + settings-nav.ts (single source of truth for structure and RBAC); classes .set-shell, .set-rail, .set-nav, .scol, .set-stack, .table-wrap in apps/web/src/ds/settings.css.

Pattern source: Attio / Linear (Mobbin), owner-approved 27 September 2026.

When to use

  • The application's own Settings area (/nastaveni) — personal preferences, company configuration, templates, AI controls, data schemas, reports.
  • Any future full settings-style menu that needs the same shape: grouped left rail, one page per item, no tabs.

When not to use

  • As a tab inside a record detail. Personal preferences and settings are not a tab of anything — they live in this menu, not under record-tabs.
  • For a simple two-way toggle inside one page (Moje | Firemní) — that is a Segmented control inside the page's own content, not a second menu.

Anatomy

  • Left rail .set-rail (--sidebar-w): back button + "Nastavení" title, search, then groups .set-group with a muted .set-grp label and .set-item rows (icon + label). A group with no visible item (RBAC-filtered to zero) renders nothing — SettingsLayout skips it, it does not show an empty header.
  • Groups (SETTINGS_NAV, 30 items): Osobní (Personal) · Firma (Company) · Šablony (Templates) · AI a automatizace (AI & automation) · Data · Přehledy (Reports).
  • Right column: SectionTopBar with the active item's own icon and name as identity (no breadcrumb — "Nastavení › Firma › …" does not exist), section actions on the right via primaryAction/SettingsPageAction portal, then .sbody → .scol (660px, margin-inline: auto).
  • Wide content: a table wider than 660px gets its own horizontal scroller (.scol .table-wrap { overflow-x: auto }); the page itself never scrolls sideways.
  • Merged pages: an item that folds together what used to be several tabs stacks its existing SectionBlocks top-to-bottom in .set-stack (e.g. Připojení = e-mail/calendar block + AI connection block; Schůzky = recording + reminders; Přístup = domains + vendor access). Content itself is recycled 1:1, not rebuilt.
  • Canvas sections (Uživatelé, Týmy, AI zaměstnanci, Import, Koš, Aktivita) keep their own chrome (own header, own scroll) and take the full right column with no settings breadcrumb bar, to avoid a double header.
  • Phone (≤720px): two-step, same shape as the ⌘K palette. /nastaveni alone shows only the full-width menu; /nastaveni/<sekce> shows only that section, with a quiet back arrow (IconButton icon="arrow-left") as the first item of its SectionTopBar.

Best practices

Do

  • Give each settings item exactly one page and one place in the menu — never split one concept across two menu entries, never duplicate a page under two keys.
  • Reuse the exact same DS Icon name in the rail item that the same concept already uses in the main app navigation (one icon = one meaning, RULES §8) — never invent a second icon for the same thing.
  • Redirect an old URL through MOVED_SECTIONS when a section is renamed or merged, so a bookmark, a doc link or a link the assistant sent still lands somewhere real.
  • Keep content in .scol at 660px and centred; widen only the scroll container of a table (.table-wrap), never the page.

Don't

  • A TabStrip / Tabs row inside a settings page as a second level of navigation — that is a new menu item (its own page), or at most one Segmented control inside the page's own content.
  • A breadcrumb in the settings SectionTopBar identity (Section top bar forbids breadcrumb identity, RULES §8).
  • A context rail next to settings content — the settings page has no record rail.
  • Explanatory body text as a substitute for clear labels (ds-no-explanatory-text rule) — one known debt still breaks this (Firma › Obecné) and is not the pattern to copy.

Content

  • Group labels and item labels are sentence case, translated via settings:groups.<key> / settings:nav.<key> (i18n, cs+en parity) — never hardcoded strings in the component.
  • The page identity name is the same string as its rail item's label, read from the same translation key (activeItem in SettingsLayout.tsx).

Known debts (not the pattern to copy)

  • Company e-mail templates have an inline editor; personal templates are a plain list — same mail_templates table, split only by scope.
  • Minutes templates (sablony-zapisu) have no UI CRUD — create/edit/delete is agent-only via MCP; the page is read-only list + "Vyzkoušet".
  • Firma › Obecné still carries explanatory body text.
  • section-top-bar, title-with-entity-tile — the bar and identity every settings page uses.
  • tab-strip, record-tabs — what a settings page explicitly does NOT stack under its bar.
  • section-block — the unit merged pages stack in .set-stack.
  • sidebar-nav-item — the app's own left navigation; the settings rail is a separate, app-local grouped nav (DS has no exported grouped-nav component for it).

Saving, anatomy and irreversible actions (acrm !4323, verified on dev 1. 10. 2026)

  • Saving: switches and selects save immediately with a short toast. A block with several fields (Profil, company forms, strategy) ends with Zrušit · Uložit at the end of the block, right-aligned, both disabled until something changes; Zrušit restores the saved values. Never a lone „Uložit změny“ in the middle of the page and never a Save button per row.
  • Anatomy: one setting = one row, label left, control right, rows separated by a line (Schůzky: recording, reminder lead time, e-mail reminder). Forms with several related fields stay a stack of fields.
  • Irreversible page-level actions (Odhlásit všude jinde, Obnovit odkaz ICS) are the LAST section „Nevratné akce“: row with the label left and a danger button right, always with Confirm (question in the title, consequence in the body, the verb on the button). Actions on a specific object (disconnect an account, delete a row) stay with the object.
  • No explanatory grey text under fields (e.g. „E-mail spravuje přihlášení…“ removed — the read-only field says it). Step-by-step connector guides on Připojení stay, they are setup steps, not UI explanation.
  • Theme: the stored dark theme applies from app start, also for Settings opened directly by URL and for the loading screen (no white flash).

Source: docs/section-top-bar/settings-page.usage.md · GitLab