Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
15 KiB
Page Layout and Action Guidelines
This document defines the binding composition grammar for headed GovOPlaN pages. Core owns the reusable anatomy; each module owns its domain actions, wording, authorization, consequences, and data state.
The cross-product UI design principles define the display-first editing model, heading-adjacent help, and module-review process. Normal overview/detail surfaces show readable facts; edit coherent settings in scoped dialogs. Use an explicit editor mode for a justified broad-editing task, not merely because a page also offers filters or contains an edit dialog (UI-02).
Required Page Frame
- Use
PageLayoutfor every headed standalone, workspace, or embedded page. - Declare exactly one semantic
archetype; do not infer page intent from themode, which controls geometry and scroll ownership only. - Use
WorkspaceFramefor a full-height module surface andWorkspaceLayoutonly where navigation/content or list/detail panes are genuinely part of the interaction. - Put page-wide feedback in
PageLayoutnotices. UseDismissibleAlertfor a recoverable warning or failure andStatePanelwhen the entire surface is loading, empty, unavailable, or blocked. - Do not reproduce shared page padding, heading, toolbar, form-grid, section, table, dialog, or breakpoint CSS in a module.
Product Side Rail
Module manifests contribute stable navigation surface identifiers, labels,
paths, icons, and default order. Core owns the side-rail composition and the
shared NavigationPreferenceEditor; modules must not fork this editor or
persist their own rail ordering.
Navigation preferences are layered in this order: module defaults, system, tenant, then user. Each higher layer may reorder or change visibility. System and tenant administrators may lock an entry visible; a lower layer can still move that entry, but cannot hide it. Personal preferences cannot create locks. An unset preference inherits the complete lower layer, while “Use inherited order” removes the current layer rather than copying its values. Unknown item identifiers remain harmless so uninstalling, disabling, or later reinstalling a module does not corrupt the rail.
The platform module response projects module, system, and tenant layer states alongside the effective user state. Editors must initialize from the layer immediately below the scope they edit, so a system or tenant administrator's personal preference is never promoted accidentally. Preference saves refresh the platform module projection. View policy, permissions, and tenant module entitlements remain independent final visibility gates; changing rail preferences never grants access.
Semantic Page Archetypes
| Archetype | Use when |
|---|---|
overview |
The page summarizes health, metrics, or several peer areas without owning one primary collection or draft. |
collection |
The primary object is a searchable/listable collection and Create, when available, applies to that collection. |
detail |
The page primarily presents one record, report, or immutable projection. |
editor |
The page owns one explicit draft with Save and Discard behavior. |
workspace |
The page coordinates several panes, stages, or task-local operations that cannot honestly be reduced to one record or draft. |
The archetype remains stable for the current interaction. A page may switch
from overview to editor when the user explicitly enters configuration
mode. It must not call a page an editor merely because a dialog or an inline
filter is editable.
Page Action Rules
Pass one PageActionBar to the PageLayout actions slot. Full-canvas
workspaces use the same contract through WorkspaceActionBar, with an explicit
workspace, collection-pane, detail-pane, or editor-pane scope. The
variant makes the surface's intent inspectable and preserves the same keyboard
and visual order across modules. ActionToolbar remains the lower-level
component for section-local controls; it is not a substitute for a semantic
page or pane action bar.
| Page kind | Leading group | Trailing group |
|---|---|---|
| Overview | Context | Reload when refreshable, then ordinary primary actions |
| Collection | Collection context such as export | Reload when refreshable, then Create at the far right |
| Detail | Object context | Reload when refreshable, ordinary primary actions, then a separated destructive group |
| Editor | Context | Dirty state, Reload if distinctly safe, ordinary primary actions, separated destructive actions, Discard, then Save at the far right |
| Workspace | Task context | Reload when refreshable, ordinary primary actions, then a separated destructive group |
Documentation is associated with text, not an action-group slot (UI-01). Put
DocumentationHelpLink in the heading component's titleHelp, use TextWithHelp
for existing contextual words, and retain FormField/FieldLabel documentation
beside field labels. Full-canvas WorkspaceActionBar surfaces can supply title
and titleHelp together on the leading side. Do not duplicate a heading or use
helpAction for a detached documentation icon. See the shared principle for
card/dialog accessibility and examples.
Reload and Create belong to the same right-aligned group, in that order. A collection-wide toolbar stays above its workspace, not inside the left tree or conditionally inside an editor. Changing selection or opening an editor must not remove it. Permission-blocked creation remains visible with an explanation. On narrow screens the trailing group wraps while retaining right alignment and the same DOM/keyboard order.
An editor's Cancel/Close action is not the same as Reset/Discard changes.
When it exits an editing mode, set discardAction.behavior to "exit": it stays
enabled for a clean draft, while Save remains disabled until there are changes.
The default "reset" behavior still requires changes. Both stay blocked during
an in-flight save; the owning page must use the shared discard confirmation before
exiting a dirty draft. Never require a meaningless edit just to leave configuration.
Deutsch: Abbrechen oder Schließen beendet den Bearbeitungsmodus auch ohne
Änderungen (discardAction.behavior="exit"). Zurücksetzen setzt dagegen einen
geänderten Entwurf voraus. Beim Abbrechen eines geänderten Entwurfs vor dem
Verwerfen nachfragen; eine laufende Speicherung bleibt geschützt.
Use Card bodyLayout="table" for table surfaces, including tables wrapped by
LoadingFrame. This removes body padding explicitly, without relying on the
number of children or negative margins. Place any meaningful explanation or
warning in a padded ContentSection; do not add a redundant tagline to every
table. Use ContentGrid for sibling cards so spacing does not depend on fragments.
The shared stylesheet also recognizes existing table-only card bodies, either
directly or through LoadingFrame; loading must not add an inset, expand the
overlay outside the card, or change the table's measured width. Keep the table
at 100% of its container. Do not reintroduce module-local negative margins,
expanded calc() widths, or padding overrides to make a table fit. Mixed
content cards retain their normal padding unless they explicitly declare the
table layout. The browser conformance matrix checks all four card edges,
loading/collapse transitions, scrolling, and row actions at desktop and mobile
widths.
Deutsch: Tabellen belegen den Karteninhalt ohne zusätzlichen Innenabstand,
auch während des Ladens. Neue Tabellenkarten verwenden ausdrücklich
bodyLayout="table"; ergänzende Hinweise erhalten ihren eigenen Innenabstand.
Negative Ränder oder modulbezogene Breitenkorrekturen sind nicht nötig.
Karten mit gemischtem Text- oder Formularinhalt behalten ihre normalen Abstände.
Use MultiSelectFilter for standalone list facets. It and DataGrid share the
same checkbox body and Select all / Deselect all behavior. null means no
restriction, [] means no matches, and multiple values mean OR within a facet.
Apply remote filters before server pagination/limits and discard stale reads.
Do not replace this with rows of toggles or implement a second checkbox menu.
The dropdown's body portal escapes clipped containers. Inside Core dialogs it
joins the existing dialog stack: Tab/Shift+Tab stay in the filter, Space toggles
the focused checkbox, and Escape closes only the filter and restores its
trigger. Long option labels wrap without widening the popup.
Keep facet definitions, URL serialization and request cancellation in one owning module adapter when the same search appears on a page and in an overlay. Do not translate an explicit empty selection into an unrestricted backend query. Keep legacy API meanings at the adapter boundary; retain scope and unrelated URL parameters when clearing filters. A query, context, account or tenant change invalidates both initial and cursor requests, including results still visible during a debounce interval.
Explorer workspaces keep collection commands in a persistent header. Files
uses Reload, Create folder, then primary Upload; frequent selected-item actions
stay near the list. Group less-common selection and connection operations in
labelled domain dialogs using Dialog, FormSection and shared action bars,
with an explicit destructive section. Do not move an overloaded toolbar into
another ungrouped row. Mail's read-only workspace has one Reload for its current
profile, folder, index and preview; narrower refreshes belong in Mailbox tools.
Do not invent a New or Save button on a workspace that owns neither workflow.
Reload must not become import, synchronization, delivery or another mutation.
Explicit Reload reads must bypass short-lived client response reuse (for
example, pass cache: "no-store" through the owning read API), including each
page of a refreshed listing. Routine navigation may retain normal deduplication.
Conformance must observe a fresh request, not just an enabled Reload button.
Tree icons/disclosure controls expand and collapse; labels select. A module's
ExplorerTree.onOpen must not toggle expansion. Use occurrence-specific node
IDs when the same semantic record appears in multiple branches; selection and
ancestor expansion must follow the clicked occurrence, not every copy.
Reload means re-fetch or re-evaluate the current surface. A page declaring
refreshable must provide it, and a non-refreshable page must not use Reload as
a synonym for Cancel, Reset, or Discard. Reload never silently destroys a dirty
draft. Create is a collection-wide action and is not duplicated in a
persistent side panel. Save is present only where the page owns an editable
draft; a read-only detail page must not display a disabled or inert Save merely
to fill the slot.
Editor bars always keep Discard and Save visible. Their required state
projection is one of clean, dirty, invalid, saving, save-failed, or
conflict, and the central component announces it through a live status label.
Clean states disable Save and reset-style Discard, but keep exit-style Cancel
available; saving disables both persistence actions. Invalid disables Save
while retaining Discard. Failed saves and conflicts keep the draft recoverable
and allow an authorized retry after the module has shown the owning error or
conflict evidence. A module may add a more specific validation, policy, or
permission blocker. The editor must register its draft with
useUnsavedDraftGuard (or a shared hook that uses the same registration
contract), so browser unload, route navigation, section changes, Reload, and
the explicit Discard path cannot silently lose work.
Reload is rendered by Core from a descriptor rather than passed as arbitrary
button markup. It can project current, stale, reloading, or
reload-failed; loading is the shorthand for reloading. A failed refresh
must preserve usable loaded data, expose its stale/failure state, and leave
Reload available for recovery. Reload goes through the same unsaved-navigation
guard as route changes.
Destructive page actions use destructiveActions; never put a danger action in
contextActions or the ordinary primary group. Core renders a persistent
visual and semantic boundary before this group. In an editor it precedes the
Discard/Save pair, keeping Save in the final keyboard and visual position.
PageActionBar controls non-editor placement and owns the standard editor
persistence buttons. Other actions continue to use central
Button, IconButton, or TableActionGroup components. When an action is
visible but unavailable because of permission, target, policy, state, or
validation, keep it in its stable slot and supply disabledReason. Do not
silently hide a normally applicable action.
Forms and Dialogs
- Compose forms from
FormLayout/FormGrid,FormSection, andFormField. - Use
FieldLabelthroughFormFieldfor every field that is not genuinely self-explanatory; record justified omissions in the owning UI ledger. - Use
Dialog,DialogForm,DialogSection, andDialogActionsfor modal work. A dialog can be domain-specific while its anatomy remains central. - Use
useUnsavedDraftGuardfor explicit Discard and guarded navigation on an editable page or dialog. - Explain irreversible or operationally consequential actions before the commit button, including reversibility and durable evidence.
Collections and Details
- Use
FilterBarfor collection query controls andDataGridfor tabular collections. Keep a single orderedTableActionGroupaction set per table. - Use
MetricGrid/MetricCardfor summary measures,CardorContentSectionfor logical sections, andDescriptionListfor labelled facts. - Add a
MetricCard.drilldownonly when the displayed measure has a useful, authorized underlying collection or detail. Name the destination explicitly (for example, “Review failed deliveries”) and preserve the current scope and filters in itshrefor action. The card itself remains non-interactive so the action is visible and keyboard-predictable. Derived, privacy-suppressed, non-enumerable, or purely informational aggregates remain plain metrics; when an ordinarily available drill-down is temporarily blocked, keep its action and providedisabledReason. - Preserve loaded data after a refresh failure and mark it stale; offer Reload as the recovery action. Distinguish initial loading, empty, unavailable, permission-blocked, conflict, success, and retry states.
Review Evidence
Every new or changed page or workspace pane must have structural evidence for
its frame, semantic archetype/scope and slot order, refresh declaration, shared
component usage, stable disabled actions, dirty guard, destructive boundary,
and module-owned help identity. Type checks enforce conditional Reload and
editor persistence props. The product check discovers all consumers, rejects
undeclared archetypes and ActionToolbar panel-header copies, and requires
semantic actions for every WorkspaceFrame route. Browser conformance confirms
keyboard order, lifecycle changes, accessibility, destructive separation,
narrow wrapping, and screenshot geometry.