PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.11.2

github
Backend
Frontend
    Developer Guide
      OverviewGetting startedUI stackApp shell & sidebarAuthentication & sessionsSystem settingsUI componentsUI patternsIcon ConventionsInput AnatomyField Hints & TooltipsButton Style StandardsEntity metadatai18n — Translations on the FrontendThe AI Tool Loop (caller-driven)Error HandlingComposables (use* hooks)
      Components
        Entity list tableComboSelectDynamicIconColorSelectorColorPickerAvatarPreviewEventCardCommandPaletteAppShellAppSidebarAppTopbarAppPageLayoutAppPageScaffoldAppPageBreadcrumbAppServerBannerBrowserClientInfoLangSelectThemeToggleSidebarAppLauncherSidebarHealthBadgeSidebarOrgSwitcherSidebarProfileMenuSidebarVersionBadgeButtonButton (async)FormPageLayoutFormLabelWithPriorityHelpPasswordChecklistChoiceboxDateWheelPickerEventToastLoadingBarMetadataLoadingJsonTableViewerNumberTickerStatCardToolbarDialogBorderedEntity Confirm DialogsSheet panelRFCErrorDialogLoginFormPasskeyButtonPasskeyEnrollmentAuthMethodsPromptDialogSessionExpiredDialogInputSliderSliderFieldSwitchFieldSelectableFieldsetBadgeValuesEditorTooltipFormPrimeFieldSmartRegexInput
      API Reference
Microservices
powered by Zudoku
Components

Sheet panel

src/lib/shell/sheets/

The application has one global side sheet. Pages and components never mount their own Sheet.Root — they call openSheet() and the single SheetHost (mounted in AppShell) renders the requested panel.

Architecture

PieceFileRole
Managersrc/lib/shell/sheets/sheet-manager.svelte.tssheetState, openSheet, replaceSheet, closeSheet, typed SheetPanelId + SheetPanelPropsMap
Hostsrc/lib/shell/sheets/SheetHost.svelteThe only Sheet.Root/Sheet.Content; resolves the panel component from a registry
Layoutsrc/lib/shell/sheets/SheetPanelLayout.svelteCanonical panel anatomy — HEAD, optional TOOLBAR, scrollable CONTENT, optional FOOT
Headersrc/lib/shell/sheets/SheetHeader.svelteHEAD chrome (title + actions snippets)
Panelssrc/lib/shell/sheets/panels/, src/lib/entity-list/sheets/panels/Registered panel components

Opening a sheet

Code
import { openSheet, closeSheet } from '$lib/shell/sheets/sheet-manager.svelte'; openSheet('shell.aiModelTestReport', { model }, { side: 'right', // default contentClass: 'w-3/4 sm:max-w-xl p-0', // Sheet.Content classes (width!) modal: true, // default }); closeSheet(); // programmatic close

Props are typed per panel id via SheetPanelPropsMap — TypeScript rejects unknown ids or mismatched props.

Panel anatomy

Every panel uses SheetPanelLayout, which enforces a fixed anatomy:

  • HEAD — SheetHeader: mandatory icon snippet + title snippet on the left, actions snippet on the right. The icon MUST be the same icon as the CTA that opens the sheet. When actions is omitted a standard close button renders. AI-assistant sheets share one convention: AiIcon
    • "AI Assistant" + a topic qualifier (global, guide, regex, json). HEAD carries secondary icon-only utilities (reset, clear, new) plus the close ✕ — never the panel's primary action.
  • TOOLBAR — optional below-head strip (border-b px-3 py-2) for tools only: search inputs, tab bars, sort segmented controls. No commit CTAs.
  • CONTENT — min-h-0 flex-1 overflow-auto; the only region that scrolls. Default padding p-2, override with contentClass. How the content is sectioned (labels, dividers, accordions, tabs) is the panel's own domain — no enforced standard. Per-row actions and inline sub-form CTAs (e.g. "Add filter") belong here.
  • FOOT — optional fixed bottom strip (border-t); never scrolls. This is where a FORM panel's primary CTA lives: a Button with a text label, class="w-full" for single actions (no Cancel — the close ✕ and outside-click already dismiss). Pickers and viewers have no footer.

Header action styles

Every CTA in the header uses one shared chrome — the same as close: inline-flex size-8 icon button, text-muted-foreground opacity-70, hover:bg-accent hover:opacity-100, icon size-4. Close renders it via Sheet.Close; other CTAs render it via SheetHeaderAction. No Button component variants, no tinted pills — the header is symmetric by rule. HEAD actions are secondary utilities only; the primary action of a form panel always sits in FOOT as a labeled Button.

Panel behaviors — three archetypes

Every panel fits one of three commit behaviors:

ArchetypeCommitCTA placementExamples
PickerImmediate — click a row/toggle → commit → closeThe row itself is the CTA; no footerCurrencySelectPanel, PhonePrefixSelectPanel, ProtocolSelectPanel, ColumnsPanel
FormExplicit — draft fields, primary CTA commits (and usually closes)FOOT: Button w-full (or flex justify-end for multi-action)BadgeValuePanel (add & edit), FiltersPanel (Apply), AiCerebellumPanel (Save), AiModelImportPanel (Add)
ViewerNone — read-only or per-row actionsCONTENT row buttons; HEAD may carry a destructive reset (e.g. ErrorsPanel clear)ErrorsPanel, VersionsPanel, VersionHistoryPanel, AiModelCachePanel

Rules of thumb:

  • Form panels never live-commit while the user edits — they stage a draft and write once on the footer CTA. (A previous live-commit $effect in BadgeValuePanel produced an effect_update_depth_exceeded loop.)
  • HEAD is never the home of a primary action — icon-only buttons carry no label, so a primary CTA there is invisible. If it commits, it goes in FOOT.
  • A single-CTA footer uses class="w-full"; do not add a Cancel button — the standard close ✕ and outside-click dismissal already cover "abort".

Sheet width

Every sheet renders at the same width — 384px (w-3/4 sm:max-w-sm on the right/left variant). Do not pass width classes in contentClass: sm:max-w-sm caps them silently. A wider sheet is a central change to Sheet.Content, not a per-call override.

Code
<SheetPanelLayout contentClass="px-4 py-3"> {#snippet title()} <span>Panel title</span> {/snippet} <p>Scrollable content…</p> {#snippet footer()} <div class="p-3"> <Button class="w-full">{$t('app.common.apply')}</Button> </div> {/snippet} </SheetPanelLayout>

Registering a panel

  1. Add the id to SheetPanelId in sheet-manager.svelte.ts ('shell.*' for shell panels, 'entity.*' for entity-list, 'config.*' for config-driven panels).
  2. Add its props to SheetPanelPropsMap.
  3. Create the component under src/lib/shell/sheets/panels/ using SheetPanelLayout.
  4. Register the component in the registry inside SheetHost.svelte.
  5. Open it from a click handler with openSheet().

Guidelines

  • Open sheets from discrete events (clicks), never from effects that watch a bindable "open" flag — that produces a close/re-open loop.
  • Do not mount Sheet.Root outside SheetHost.
  • Do not re-create the anatomy inside a panel — SheetPanelLayout owns it.

Next steps

  • UI patterns — popover vs dialog vs sheet
  • App shell — where SheetHost is mounted
Last modified on October 28, 2026
Entity Confirm DialogsRFCErrorDialog
On this page
  • Architecture
  • Opening a sheet
  • Panel anatomy
    • Header action styles
  • Panel behaviors — three archetypes
    • Sheet width
  • Registering a panel
  • Guidelines
  • Next steps
TypeScript