Last Updated: 2026-06-02
Status: Active
Owning Module: PF (Platform Foundation)
Note: This guide is the single canonical reference for all settings page patterns.
Overview
This guide documents the standardized pattern for module settings pages across all cores. All settings pages should use shared components for consistency and maintainability. For current Platform Settings module surfaces (hub/routes/exports), also see:src/platform/settings/README.mdsrc/platform/settings/pages/data-migration/README.md
Architecture
Platform Settings Hub Integration Checklist
When adding a new platform settings page (/settings/*):
- Add the lazy route in
src/routes/platform.tsx. - Add/verify the correct
RequirePermission(...)guard. - Add a Settings Hub card (if it should be user-discoverable from
/settings). - Keep card visibility and route permission checks aligned.
- Confirm loading and error states use shared settings components.
Quick Start
- Prefer
PageContainer/SettingsPageLayoutinstead ofcontainer mx-auto. - Include
docsPathon everySettingsPageHeaderso the docs link is consistent. - Wire
isDirtyfrom your form (react-hook-formformState.isDirty) when you enable the unsaved navigation guard. - Call
transformSettingsForDbin module upsert hooks or submit handlers before writing nullable columns. - Run
npm run audit:settings-consistencywhen changing settings routes or page shells.
Component Reference
SettingsPageHeader
Located at@/platform/settings/components/SettingsPageHeader
Provides a standardized header with:
- Module icon
- Title and description
- Optional documentation link
Legacy Interface (also supported):
SettingsLoadingSkeleton
Located at@/platform/settings/components/SettingsLoadingSkeleton
Provides a loading skeleton that matches the settings page layout:
Renders:
- Header skeleton (title + description)
- Tab bar skeleton (if using tabs)
- Form field skeletons
SettingsErrorState
Error display with retry option.Settings Hook Pattern
Each module should have a standardized settings hook. The recommended pattern uses a singleupsert mutation for simplicity and consistency.
Recommended: Upsert Pattern
The preferred pattern uses a singleupsert mutation (as seen in LO, RH, and all other modules):
Upsert Hook Return Interface
All module settings hooks now return a consistent interface:Settings Page Usage
With the upsert pattern, settings pages are simplified:Legacy Pattern (Update-based)
For reference, the older update-based pattern is still supported but not recommended for new implementations:Mobile Responsiveness
Settings pages must be mobile-responsive:Desktop (md+)
- Full tab bar visible via
ScrollableTabsList - Side-by-side form layouts where appropriate
Mobile (< md)
ScrollableTabsListhorizontally scrolls; no second Select dropdown needed- Single-column form layouts
- Touch-friendly inputs (44px minimum)
Do NOT use aTabsList/Selectsplit for mobile.ScrollableTabsListhandles all viewport sizes with horizontal scroll and touch-swipe. UsingTabsListdirectly or a manualSelectfallback breaks the tab URL-sync pattern and violates the accessibility requirement (aria-labelonScrollableTabsList).
Form Organization
Tab Structure
Organize settings into logical tabs:Form Layout
Dirty State Management
Track unsaved changes:Module Compliance Status
All modules now use the standardized upsert pattern:Existing Implementations
Reference these for patterns:Best Practices
- Never return null for loading states - Always use
SettingsLoadingSkeleton - Always include docsPath - Link to relevant admin documentation
- Use semantic icons - Choose icons that represent the module
- Handle no-organization state - Show a helpful message
- Use consistent card structure - CardHeader with title/description, CardContent with form
- Use upsert pattern - All modules now use this pattern for consistency
- Always use shared components - Don’t create custom headers/skeletons
- Handle all states - Loading, error, empty, success
- Validate before save - Use form validation
- Show save confirmation - Toast on success
- Warn on unsaved changes - Block navigation if dirty
- Keep tabs focused - 3-5 settings per section max
- Use appropriate inputs - Switch for boolean, Select for enum