-
Notifications
You must be signed in to change notification settings - Fork 27
Design System
Contained's UI is built around a small Liquid Glass design system. Prefer these components before adding one-off surfaces.
- A morph panel is one stable shell. Content can swap inside it, but the shell should own size, clipping, backdrop, and elevation.
- Search fields and toolbar controls should feel like compact macOS controls, not large mobile bars.
- Cards own their content clipping. Parent scroll views own viewport clipping.
- Nested cards inside panels are flat by default; the enclosing shell owns the elevation.
- Personalization is local app state. Tint, icon, nickname, and background styling should not be written back to container labels.
Style ownership:
-
Personalizationis the resolved card style. -
WidgetConfiguration,GraphStyle, andWidgetInterpolationown card metric-widget schema and graph options. -
PersonalizationStoreowns persistence, inheritance, backup, and legacycontained.*label migration.
Use MorphPanelScaffold for toolbar panels. It provides the shared chrome,
content, and footer structure used by Images, Templates, Activity, System,
Settings, and the Command Palette.
Guidelines:
- keep panels anchored to their toolbar source when possible
- use
PanelHeaderfor titled panels - omit
PanelHeaderwhen the primary control is itself the header, such as the Command Palette search field - keep footer hints compact and secondary
Use PanelSection, PanelRow, PanelField, and PanelToggleRow for dense
settings and editor surfaces inside glass panels. This keeps Customize, Run/Edit,
registry login, image build, and Settings aligned on one row rhythm and one
info-button placement model.
Guidelines:
- keep sections top-level; avoid nesting glass cards inside glass cards
- put explanatory help in the row
infoslot instead of appending ad-hoc trailing info buttons - split repeated editors into focused subviews when the parent sheet also owns persistence or presentation state
- use
SheetHeaderfor modal sheets andPanelHeaderfor in-window morph panels or embedded panel pages
The floating toolbar and toolbar-panel navigation are separate experimental
settings. experimentalToolbarUI turns on the custom top/bottom toolbar chrome;
experimentalPanelNavigation decides whether eligible routes open morph panels
or fall back to classic pages and sheets.
AppToolbar is mounted inside the NavigationSplitView detail column by
ClassicShell, not across the whole split view. The detail body receives top
padding from AppSafeAreaManager, while the sidebar and bottom page edge keep
native split-view layout. Scrollable page interiors add bottom scroll-content
clearance for the floating toolbar, so the last row can move above it without
lifting the page itself. Toolbar page actions live in the top row to the left of
search; page filters live in the bottom row next to System and hide on pages
without filters.
Bottom page filters use the shared toolbar menu-button shape. Containers, Images, Templates, and Networks all expose their page-specific grouping/sorting state from this slot rather than inventing page-local controls.
Contextual page controls act on the current page. They should switch page or subpage state directly rather than opening morph panels. Global toolbar buttons and menu commands own panel presentation.
When the sidebar is visible, the top-row vanity traffic-light house and page switcher are hidden because native sidebar navigation owns that role. Contextual page controls remain in the leading toolbar cluster, immediately before search.
When toolbar panel navigation is enabled, panel-owned destinations such as System, Activity, and Settings are removed from page navigation and remain available through their toolbar/menu entry points.
Expanded resource cards opened from full pages should receive the same toolbar safe-area contract as morph panels, clearing both top and bottom toolbar bands.
Use ResourceGlassCard for containers, images, tags, volumes, networks, and
palette result cards.
Recommended internal pieces:
-
ResourceCardHeaderfor the top row -
ResourceCardIconChipfor icons and symbols -
ResourceCardTitleTextfor names -
ResourceCardSubtitleTextorResourceCardMonospacedSubtitleTextfor metadata -
ResourceBadgeTextfor compact state or kind labels -
ResourceCardFooterMinifor small footer actions and metrics
Use isSelected instead of inventing a second selection ring. Use elevated: false for cards inside already-elevated morph panels.
The palette should not degrade rich app objects into plain text. Use
PaletteItemVisual whenever a result has meaningful state:
-
.containerfor actual containers -
.imageGroupfor local image groups -
.imageTagfor local tags -
.volumeand.networkfor resources -
.tintfor appearance color choices
Plain rows are reserved for generic actions such as refresh or opening a page.
Use Tokens for spacing, radius, toolbar dimensions, panel sizes, icon sizes,
and shadows. If a new value appears repeatedly, add a token before duplicating
magic numbers.
Important groups:
-
Tokens.Toolbarfor toolbar band and control sizing -
Tokens.PanelSizefor morph target sizes -
Tokens.Spacefor layout rhythm -
Tokens.Radiusfor card and control rounding -
Tokens.IconSizefor chips and toolbar controls
UI changes should run:
swift test
git diff --check
./scripts/bundle.sh debug
open Contained.appFor Contained UI work, relaunch the built app after passing tests so the current
panel/card behavior is visible in the running app. If an existing Contained.app
is open, quit it before rebuilding and reopening the fresh bundle.
Contained — source-available under PolyForm Noncommercial 1.0.0. Free for non-commercial use.