UI Theme Guidelines¶
The frontend theme is anchored by the shared shell and UI tokens in
frontend/src/index.css, plus reusable class exports in
frontend/src/components/ui/styles.ts.
Tokens¶
- The default brand color is Reef Blue
#0569f8. Runtime branding derives the complete light and dark primary scales from the configured Admin color. - Reef Coral
#fc572cis a decorative brand accent reserved for authentication backgrounds. It is not a warning, error, or destructive action color; keep success, warning, and danger on their semantic tokens. - Use primary shade
700for foreground links and accents in light mode and shade200in dark mode. Primary buttons may use the base or strong primary token with white text. - Use
shell-*tokens only for the app shell, topbar, sidebar, and their controls. - Use
ui-*tokens for workspace content, cards, panels, forms, tables, dialogs, toolbars, and inline states. - Prefer
--ui-surface,--ui-surface-muted,--ui-border,--ui-border-soft,--ui-text,--ui-text-muted,--ui-hover,--ui-selected-bg,--ui-focus-ring, and--ui-shadow-softover local Tailwind color chains.
Primitives¶
- Cards and panels: start with
ui-surface-card,ui-surface-muted,uiCardClass,uiCardMutedClass,uiPanelClass, oruiPanelMutedClass. - Tables: use
ui-data-table,uiDataTableClass, anduiTableContainerClassbefore adding page-specific table classes.components/list/listPresentation.cssowns the shared--list-*tokens; the formermanager-tableandcompact-tablestyle layers are retired. - Toolbars and filters: use
ListToolbar,uiToolbarClass,uiToolbarSecondaryClass, shared compact toolbar classes, andActiveFiltersBar. - Compact operational summaries: use
InlineSummaryfor label/value pairs. - Buttons: use
UiButton,uiButtonBaseClass,uiButtonVariants, oruiIconButtonClass; keep custom button chains for exceptional states only. In listings, useListActionButton/ListActionLinkandListActionsinstead. UseListActionAnchorwhen native navigation is required, such as opening another workspace in a separate tab. They own geometry, focus, disabled/loading states and semantic variants without changing the default form-button scale. - Forms: use
ui-control,uiInputClass,uiLabelClass, anduiCheckboxClass. UseSettingsSwitchfor binary settings, reserving checkboxes for selections and acknowledgements. Switches use theme primary for on and--ui-text-mutedfor the neutral off track; green remains a semantic success color, not a fixed switch color.UiFieldowns label/help/error associations and merges additional description IDs from its caller. Standard inputs, selects and textareas retain all those descriptions and never suppress a field error with an explicit false ARIA state.ui-control[aria-invalid="true"]uses the semantic danger border; errors must also have visible text. Modal controls keep 44px touch targets.size="compact"onUiInput,UiSelectandUiTextareauses the sharedui-control-compactclass: 12px text, an 18px line height and a 28px minimum height, increasing to 44px on mobile or coarse pointers. This explicit size class takes precedence over the standardui-controltypography; do not recreate compact field sizing at individual call sites. - Selection: line tabs use a 3px primary underline and primary
700text in light mode /200in dark mode. Sidebar links, including Profile, reuse the shared active styling and vertical primary marker. Keep shell backgrounds onshell-*tokens and content backgrounds onui-*tokens. - Use primary
500in light mode and400in dark mode for active tab/sidebar markers and switch tracks. The lighter dark-mode shade keeps these small indicators legible, including with darker custom branding colors. - Keyboard focus on line tabs and switches uses an opaque primary
700/200outline so it remains visible on both themes and with custom branding. - Modals and menus: use
Modal,uiMenuClass,uiMenuItemClass, shell menu classes in the topbar, andAnchoredPortalMenufor positioned menus.components/modal.cssbounds the entire dialog to the dynamic viewport, removes inherited list/form spacing from its overlay and lets the body scroll within the remaining height. Long titles wrap; exceptionally tall headers scroll within half the available height. The close action keeps a 44px target below 1024px or with a coarse pointer. Settings dialogs and shared confirmations use the compact spacing contract. Do not compensate for viewport issues with page-specific modal sizes. - Destructive confirmations reuse
ConfirmActionDialog, including Admin connection, endpoint, RGW account and RGW user deletion. Put optional deletion choices in itsoptionsslot (disabled while loading), resource constraints inwarningwithwarningTone="warning", and request failures inerror. Keep resource counters, identifiers and backend deletion guards intact. - Admin RGW creation reuses
AdminRgwCreateFields; creation and import shareAdminRgwEndpointFieldand named, field-adjacent validation. UseSettingsFormwithpresentation="dialog"insideSettingsDialog, or its page footer for a workflow page.submitDisabledblocks submission without freezing editable choices;busyfreezes the draft and dismissal until the request completes. Keep successful import forms clean after clearing their identifiers. - Badges and tags: use
UiBadge/UiTagBadgeand their shareduiBadgeShapeClass(4px radius, 1px border), pale tone fill and medium text, without shadows. Preserve semantic tones and custom tag palettes in both themes; contextual markers useprimary. Keep list-specific compact sizes.
Patterns To Avoid¶
- Do not add workspace-scoped themes when a shared token or primitive fits.
- Avoid
bg-gradient-*,backdrop-blur,shadow-xl,shadow-2xl,rounded-xl, androunded-2xlon standard workspace surfaces. - Keep strong shadows, translucent overlays, and larger radii for justified cases: authentication screens, popovers, overlays, alerts, and temporary operation states.
- Do not use visual refactors to change backend contracts, permissions, IAM/S3 semantics, routes, or execution context behavior.
Documentation Theme¶
The published MkDocs theme prioritizes reading and a simple documentation tree. It retains the BucketReef logo and blue accent, with its own neutral surfaces and spacing rather than mirroring the application's workspace density.
doc/docs/assets/stylesheets/docs-theme.cssowns shared--docs-*tokens for the main theme and screenshot controls, with light and dark palettes.- Use white article surfaces, light gray navigation, fine borders, 4px radii, and no decorative shadows. Callouts retain their semantic colors.
- Use Arial/Helvetica without external font loading, readable body text, moderate heading weights, and a wider, plainly indented navigation tree.
- Keep Material's responsive navigation and search. Tables and code must scroll locally when needed, with visible keyboard focus on interactive controls.
- Documentation styling does not alter the application theme or persisted Admin branding settings.
- Follow Docs Maintenance for exact typography/layout defaults and the required build, screenshot, and desktop/mobile interaction checks.
Compact settings tokens¶
components/settings/compactSettings.css owns the opt-in settings geometry:
--settings-content-width (1120px), --settings-title-width (190px),
--settings-section-gap (12px top/bottom), --settings-column-gap (16px),
--settings-row-height (40px minimum), --settings-row-padding (6px) and
--settings-control-height (28px desktop / 44px below 1024px or with a coarse
pointer). Change these shared tokens instead of copying dimensions into page-specific styles. Rows
may grow for translated labels; do not clip text or reduce mobile targets.
Compact sections, inline status badges and the sticky action area use the
existing surface, border, text and primary palette tokens. Page-level sticky
action bars use ui-page-sticky-actions. While a bar is present outside a
hidden tab, the main scrollport drops its bottom padding so bottom: 0
reaches the visible edge without revealing scrolling content below the bar.
Workflow panels also remove their bottom padding so the bar stays flush with
the panel border at the end of the form, without negative bottom margins.
Pages without a visible bar retain their normal bottom gutter; sidebar and
dialog footers keep their own scroll containers. Validate at the top, middle
and end of a long form, after switching tabs, and after cancelling a draft.
Branding previews
scope generated primary variables to their demonstration container; they must
not call the global branding runtime until a server save succeeds. Check both
light and dark themes and a custom accent. Switches represent binary settings;
checkboxes remain for multiple selections and acknowledgements. Inheritable
booleans use an explicit three-state selector, with a separate Customize switch
for numeric and list overrides.
The compact presentation is explicit. Nonmigrated consumers keep the default
SettingsLayout presentation, including bucket configuration. Account Portal
overrides and Storage Space settings now use the compact presentation. The former
Portal alias facade and unused card/form helpers have been removed; import the
canonical components directly.
Admin UI User and UI Group editors also use the compact settings sections and
SettingsForm footer. AdminUserIdentityFields owns the shared user identity
fields, while AdminAccessToggleSection presents all direct or inherited access
switches. Preserve the distinct grant semantics and the Manager-before-Browser
tab order. Pending saves freeze the draft and return controls; group close
guards include pending association selections and image changes. The user
Authentication tab keeps its immediate actions and Done footer: it must never
submit the parent profile draft.
The Authentication tab uses the same compact sections for passkeys, passwords,
external identities and sessions. Its native fieldsets submit their own immediate
action on Enter and freeze both their fields and the parent editor navigation
while an operation or passkey verification is pending. Loading errors offer a
read-only retry; mutation errors remain beside the action or inside its existing
confirmation through ConfirmActionDialog.error. Keep the exact WebAuthn guard
detection, explicit verification and single retry contract in
useRecentWebAuthnStepUp; its dialog uses SettingsDialog and shared actions.
API token creation also uses SettingsDialog, native fieldsets and compact
checkbox choices. Validate required names, scopes and whole-day expiry beside
their fields; blank expiry delegates to the server default. Freeze the draft and
close paths during creation, including explicit passkey verification. Keep the
one-time secret panel and use shared SettingsButton actions for copying or
hiding the newly created token. Copied examples resolve the configured API base
against the current origin rather than assuming a local backend address.
Short editable dialogs can compose SettingsFormDialog, which reuses
SettingsDialog, SettingsForm and ModalActions. Portal membership and quota
requests, space creation/import, public links and raw-log exports use this
composition. It owns the 12px field spacing, native form validation, translated
Cancel/Done controls, first-field focus, inline submission errors and pending
fieldset/close locks. The async submit callback must return its promise so the
dialog can suppress repeated submissions until it settles.
Mount one instance per draft and supply a stable draftKey containing only
editable values. Closing a changed draft uses the shared discard confirmation;
route departures and reloads are protected too. onClose("navigation") must
clear local state without rewriting the destination URL. After a successful
save that navigates, unmount the dialog before navigation so the saved draft
does not trigger a discard prompt. A completed public-link dialog keeps its
copy action and Done control, clears its dirty state and hides creation.
Callers retain API payloads, permission checks, field validation and resource
context; membership identity/reason fields are shared in PortalRequestFields.
Page-history load errors stay outside these dialogs; submission errors stay
inside, alongside the retained draft so the user can retry.
Browser bucket/folder creation uses the same dialog composition. Creation hooks
own validation, S3 requests and post-create refresh; presentation owns close
confirmation, focus and navigation guards. Pass the submit promise through
without void wrappers. Preserve bucket-name normalization and literal parent
prefixes, spaces and repeated separators in folder keys.
The SSE-C editor has multiple actions and composes SettingsForm with
useSettingsFormController. Secondary actions use runAction so Generate,
Clear and Enable share the pending lock and cannot overlap. Its hook owns the
accepted draft baseline because generation immediately activates a key. Keep
Show/Hide, memory-only scope, key validation and the manual-copy handoff; do not
change signing or encryption semantics to match presentation.
Manual Browser copy uses SettingsDialog, a labelled read-only UiTextarea,
selected text, shared actions and an announced result. A missing clipboard API
and a rejected clipboard permission both open the same fallback for paths and
presigned URLs. A failed presign must not open a copy dialog with no URL.
Read-only collection dialogs compose ListDialog: it shares SettingsDialog
geometry, an unframed ListToolbar, loaded counts, refresh/pagination controls,
and announced loading, error and empty states. Retain existing rows while
loading or after pagination failures; an initial failure must not look like an empty
collection. The modal body owns vertical scrolling, with no extra height-limited
list nested inside it. Descriptions and long identifiers wrap within the dialog.
Browser prefix versions and multipart uploads use responsive DataTableShell
rows with this composition. Counts describe loaded rows, not a server total;
exports retain the exact keys, versions and prefix. Keep row-action confirmation
and execution in the existing callers.
The Browser operations overview uses the same collection chrome while retaining
its timeline cards, group pagination and operation-specific actions. Use shared
listing buttons and badges, aria-pressed for filters, aria-expanded for file
groups and a named progress bar. Closing this read-only view never cancels work.
Browser bulk attributes, restore-to-date and old-version cleanup also use
SettingsFormDialog because their targets belong to the current selection or
prefix. Keep the target summary visible and use UiInput, UiSelect and
UiTextarea with associated labels. Conditional attribute groups share the
same 12px field rhythm and soft separators; metadata fields use two columns
when space allows. Restore preview keys wrap without normalizing or truncating
the object key. Operation hooks retain S3 payloads, version rules, cancellation
and partial-result summaries. Return their apply promise to lock fields, close
paths and repeated submissions while an operation runs. A dry-run result does
not complete the draft: the user can review it and run the restore.
Browser context changes, like Manager context changes, navigate through ctx
before changing provider state or stored preferences. The catalogue derives the
executor after navigation is accepted, so a keyed outlet cannot erase a draft
before its route guard runs. Do not expose an eager context-state setter.
Native bucket/prefix history entries may keep the same URL. While any modal
owns the Browser interaction, reject those local history transitions too;
otherwise Back can silently change the targets beneath a pending operation.
The modal's Close/Cancel controls keep their existing draft confirmation.
Contextual drawers yield Escape and focus trapping whenever hasOpenModal() is
true, including pending dialogs with Escape disabled. A covered drawer must not
close or rewrite its object URL while the user operates a child dialog.
Editable workflow pages use SettingsWorkflowForm, combining WorkflowPage,
compact settings sections and the shared sticky SettingsForm footer.
useSettingsFormController gives these pages and SettingsFormDialog the same
native submission, pending lock, discard and navigation contract. A loading
page freezes fields and submission but permits leaving; read failures expose a
retry action and must not make a fallback draft writable.
Manager SNS creation uses SettingsFormDialog; attributes and policy use
SettingsWorkflowForm. Each editor owns its load and draft, ignores late
responses after unmount, and accepts a new baseline only after loading or
saving succeeds. Submission errors retain the current draft. Capture the topic
ARN and execution context when opening an action: a context-selector update
must not retarget an existing draft or remove it before navigation confirmation.
Manager's context selector navigates to the new ctx before the provider changes
the executor; eager selection must not remount its keyed outlet before a draft
or pending-operation guard can decide. Clean editors close when the context
changes. Topic deletion uses a controlled
ConfirmActionDialog so failures remain visible and retryable, with dismissal
and navigation locked during the request. Keep native SNS attributes, policy
JSON and executor parameters in the feature layer.
Manager IAM user, group, role and policy creation, plus role trust-policy editing,
use SettingsWorkflowForm with a plain surface. Their identity, managed-policy
selection and inline-policy fields retain the same compact section layout.
The shared wrapper owns the native submit, pending caption, sticky footer and
close/route/reload guards; feature handlers retain IAM documents, attachments,
validation and one-time access-key presentation. Errors appear once inside the
active form. Do not reintroduce local close guards around these forms.
Manager bucket creation follows the same SettingsWorkflowForm contract with
General and Protection sections on one page. Keep the execution context visible,
associate labels with the bucket name and optional LocationConstraint, and use
the shared switches and action footer. The feature layer retains S3 name
normalization, endpoint-default placement and versioning payloads. Failed
creation retains the draft; pending creation and list refresh lock resubmission,
closing and context navigation.
Feature-rule inspection retains the specialized grouped FeatureRulesTable.
Its JSON opens in a SettingsDialog with a labelled, read-only UiTextarea,
using theme colors and wrapping long values for narrow screens. Context or
feature changes clear the previous inspection and rows; failed inventory loads
use the error state rather than the filtered-empty state.
Portal external-tool access creation uses SettingsWorkflowForm with compact
Recipient and Access sections. SettingsChoiceRow supports named native radio
groups as well as independent checkboxes; keep the input's native keyboard
behavior and the same row presentation. Creation retains the personal IAM key
limit, owner/manager space eligibility, external scope and one-time secret panel.
The form guards dirty drafts and pending creation; retries keep the recipient,
space and permission. A tools page is mounted for one project so late reads
cannot replace another project's keys or expose the previous creation result.
PortalToolConnectionDialog owns its space load and ignores results after it
closes. It uses SettingsDialog, shared controls and compact application rows.
Load spaces only when the configurator is needed; retain selection on reopening
and show copy/download feedback inside the dialog. Keep existing-key scope,
endpoint addressing and secret-free download generation in feature helpers.
Portal space creation, import and collaborator invitation reuse
PortalAccessModeFields and PortalShareCandidatePicker. The team default role
appears only for Team access. The picker uses a named ListToolbar, shared
checkboxes and settings controls; keep one selection count and wrap identities
instead of truncating them. Existing grants remain disabled and show their role.
PortalAddPeopleWorkflow owns the space-scoped catalogue and selected roles,
with explicit loading failures, retry and the shared draft/pending guard.
PortalCollaboratorRequestDialog reuses the membership identity fields and
settings form controller. Render its form in a DOM portal and stop settings
form submit propagation so it cannot submit the enclosing invitation. The
enclosing workflow aggregates the child dirty/busy state into one navigation
guard; explicit dialog cancellation guards only the membership request draft.
Keep Admin membership requests distinct from space invitations.
Browser bucket/prefix history must use router navigation and router user state.
Raw history.pushState entries duplicate the router index and can bypass a
later page's unsaved-change blocker, including after leaving the embedded
Portal file explorer. Preserve literal prefixes and let the router own exits
from the explorer.
Portal folder restoration and history cleanup use PortalOperationLayout: a
plain WorkflowPage, compact metadata, the shared sticky action bar and a
pending-operation navigation guard. Progress uses UiProgressBar with no
percentage until the candidate total is final. Results use InlineSummary,
retain partial counts and disclose truncated failure details; avoid nested
metric cards or local progress animations. Stopping is an operation control,
not a destructive action, and does not undo work already completed.
usePortalStreamOperation owns one stream per mounted space, rejects duplicate
starts, aborts on unmount and ignores late responses. Defer automatic cleanup
start past React's mount probe to avoid a start followed by immediate abort.
Keep manual cleanup behind its existing confirmation; do not automatically
retry interrupted or failed operations. Only completed cleanup results announce
success; canceled or failed results remain visible with their partial counts.
useSettingsRemoteDraft is shared by SNS and IAM role editing. Mount it for one
resource and execution context, provide a stable load callback, and disable
editing/submission until the remote baseline is available. Its retry control
must remain operable after a read failure. A closed editor ignores late load
responses. Role names and paths remain read-only during trust-policy editing.
Bucket selection dialogs in Ceph Admin and Storage Ops use SettingsDialog,
ModalOptions and ModalActions for the same compact geometry. Configuration
backups use SettingsFormDialog: selected features survive capability-list
refreshes, unavailable features cannot be submitted, and pending downloads
freeze both the form and closing. Selection exports remain immediate format
actions with progress in the workbench.
UI tag operations retain custom drafts until the operation succeeds, show
submission errors inside the dialog and guard closing/navigation. Existing-tag
actions do not discard a separate new-tag draft. The selection-action hook
returns an error message on failure and null on success so the dialog can
retain its draft without changing tag API payloads or target resolution.
Tag settings popovers share the menu surface and compact settings controls;
they own Tab and Escape until closed and return focus to their tag trigger.
Hide a tag popover while its visibility confirmation is open so the topmost
confirmation owns Escape.
Comparison confirmations use ConfirmActionDialog and its detail/impact
slots. Manager remediation must retain the source and target execution
contexts, exact object keys, cutoff and destructive/truncation warnings.
Ceph Admin navigation confirmations retain the full object key. Confirmation
only starts the existing workflow; its execution and progress stay in the page.
OneTimeSecretPanel owns the compact handoff presentation for generated API
tokens and S3 keys across Admin, Manager, Ceph Admin and Portal. Reuse its warning
palette, standard badge, labelled value groups and shared copy controls. Each
copy reports success or a recoverable failure beside that value; Portal supplies
localized feedback. Keep values intact and selectable, including long strings,
and keep workflow-specific actions and secret lifetimes in the caller.
Settings typography is also tokenized: titles 14px/20px at weight 600, labels
13px/18px at weight 500, body/inputs 13px/18px at weight 400, descriptions and
buttons 12px/16px at weight 400. Use settings-section-title, settings-label,
settings-body, settings-description and SettingsButton; do not depend on
ad-hoc tiny text utilities or change the global UiButton defaults.
Dialog and action-bar spacing comes from --settings-dialog-padding-x/y,
--settings-field-gap and --settings-actions-padding. Heights are minimums,
so longer translated labels and validation messages can wrap without clipping.
Listing tokens and exceptions¶
The --list-* scale centralizes 12px/18px text, 400/500/600 weights, 36px
minimum rows, 12px/4px cell padding, 28px controls with 8px inline padding,
6px action corners and 4px badge corners. Below 768px or with a coarse pointer,
controls use 44px targets. Do not use arbitrary text-[12px] utilities as a
substitute for the semantic classes; the global typography layer may normalize
those utilities differently.
Surfaces, borders, hover, selection and focus derive from --ui-*; primary
variants use the runtime brand scale. Success/warning/danger action colors and
association category colors are centralized in listPresentation.css.
ListBadge reuses UiBadge semantic palettes, while custom S3 tag colors remain
owned by the existing tag contract. Avatars, progress tracks and status dots
retain their geometric meaning.
A page may set column widths, scrolling, grouped-row geometry or Browser
density. It must not override action fonts, padding, corner radius or colors
with local chains. Desktop ListActions stays on a single line; mobile groups
wrap. PageHeader/PageShell opt in with actionPresentation="listing" and
page context inputs reuse ui-list-control, so unrelated form headers and
controls keep their existing appearance. See the
presentation inventory for the exceptions
and validation command.
Consultation toolbar scope¶
ListToolbar and ListPageSection require page or section. Their
data-list-variant and ui-list-toolbar-* classes own the heading, search,
filter, tools, count and secondary zones. Search is 18rem maximum, flexible
on desktop and full-width below 768px. Filter selects are bounded to 14rem
and their labels remain inline. Use the form primitives' labels rather than
local uppercase spans. UiField marker classes change presentation only
inside these toolbar zones; ordinary forms retain their existing defaults.
Mobile sort controls use ui-list-mobile-sort; they are hidden while table
column headers are visible. Table cell and row-action geometry is independent
of these header variants. Browser does not opt into the new header zones.
Compact dashboard geometry¶
components/compactDashboard.css owns the explicitly adopted dashboard scale:
12px gaps, 12px vertical / 16px horizontal panel padding, 14px/20px semibold
panel titles, 12px/18px text, and 22px/28px metric values. Dashboard actions
wrap when necessary and use 28px minimum height, 12px normal-weight text and
6px corners; targets are at least 44px on mobile or any coarse pointer.
Badges retain shared semantic/theme colors and 4px corners.
At 1280px and above, the operational grid is 2:1; the secondary activity/map grid is 1:1. Both stack below that breakpoint. Summary links use six columns, three from 768px, and two below. Endpoint rows are at least 36px on desktop and expand with long text. The map retains its explicit 220px height. This geometry is distinct from settings and listing geometry; changes must not implicitly restyle nonadopting consumers or global UiButton defaults.
Manager and Portal explicitly pass presentation="compact" to the KPI row.
Its minimum is 120px rather than the legacy 164px, with 32px icon bubbles,
20px icons, 12px/18px labels and 22px/28px values. Heights grow with content;
never clamp translated trends or drop quota details to fit. The storage chart
retains its 92px drawing area and the data-type donut its 128px height.
Compact storage charts use the primary theme token without changing their
points or semantic distribution colors. Navigation cards, health cards and
data-type cards retain their original defaults outside this explicit adoption.
Use dashboard action links for buttons and WorkspaceDashboardLinkRow for
rich shortcuts; retain plain identity links and their accessible touch areas.
Manager/Portal rows opt into ui-dashboard-equal-row for stretching their
cards to the tallest content in each actual grid row. Apply
ui-dashboard-equal-cell to intermediate grid cells and unavailable frames
to carry the stretch through to the panel, including the data-type card.
These classes set no fixed height and do not stretch panel contents. Keep
the incident heading inside its panel so its content stays at the top.
KPI minimum heights and chart drawing areas stay unchanged; other dashboard
consumers retain their defaults.