Design System & Admin Theme Settings
Design System workspace: multi-theme management, density, visual styles, personal overrides, custom CSS, and live component preview.
On this page
SveltyCMS’s admin theme system lets you create, switch, and customize multiple named themes, each with its own density, visual style, features, and custom CSS. Changes preview instantly before saving.
Sole route: /config/design-system (Config → Design System). There is no separate Appearance page.
Deep links (query ?tab=):
| Tab | URL | Audience |
|---|---|---|
| My Overrides | /config/design-system?tab=overrides |
All users (linked from /user Settings) |
| Live Preview | /config/design-system?tab=preview |
Component catalog + token readout |
| Themes / Presets / Layout / Style / Features / Advanced | ?tab=themes etc. |
Workspace admins |
🎨 Multi-Theme Management
SveltyCMS supports multiple admin themes stored in the database. Each theme stores its own density, card variant, feature toggles, and custom CSS independently.
Themes Tab
Navigate to Config → Design System → Themes.
| Action | Description |
|---|---|
| Activate | Switch to a different theme. Applies all settings instantly. |
| Clone | Copy an existing theme as a starting point for a new variant. |
| Delete | Remove an unused theme. Cannot delete the active or default theme. |
The active theme shows a green Active badge. The system default theme (created during setup) shows a Default badge and cannot be deleted.
Creating a Theme
- Navigate to Config → Design System → Themes.
- Enter a name (e.g., “Midnight Blue”, “High Contrast Reader”).
- Click Create.
New themes start with the current density, variant, and features as defaults. Switch to other tabs to customize before saving.
Clone a theme to experiment with variations without losing your working settings. If you don’t like the clone, delete it.
📐 Layout & Density
The Layout & Density tab controls spacing, sizing, and overall compactness of the admin interface.
| Density | Sidebar Width | Header Height | Best For |
|---|---|---|---|
| Compact | 200px | 48px | Developers, data-heavy table views, translators |
| Cozy | 240px | 64px | Standard content editing (default) |
| Spacious | 300px | 72px | Distraction-free writing, managers, accessibility needs |
Changing density updates the sidebar width, card padding, button sizes, and font sizing across all 37+ native UI components in real time.
Density only affects the admin panel. Your public-facing website uses its own theme system and is not impacted.
🖌️ Visual Style
The Visual Style tab controls card borders and shadows.
| Variant | Border | Shadow | Effect |
|---|---|---|---|
| Flat | None | None | Minimal, modern, distraction-free |
| Bordered | 1px | Subtle (1-3px) | Clear card separation (default) |
| Elevated | 1px | Prominent | Cards “pop” with depth perception |
These affect every card, modal, and content container in the admin panel. The preview cards in the settings page update in real time so you can see the effect before saving.
🔧 Theme Features
The Features tab provides five toggles plus layout region controls:
| Feature | Effect |
|---|---|
| Sticky Action Bar | Save/Delete buttons stick to the bottom on scroll in content forms |
| Collapsible Sidebar | Users can collapse the left navigation sidebar for more screen space |
| Branded Login | Shows tenant logo and custom colors on the login page |
| High Contrast Mode | Enforces WCAG AAA contrast ratios across the entire admin panel |
| Reduced Motion | Disables all animations and transitions globally |
| Collections Position | Moves the Collections tree to the left sidebar, right sidebar, or both |
Layout Region Repositioning
Each theme can reposition the Collections tree independently of visual style:
| Position | Effect |
|---|---|
| Left | Collections in the left navigation sidebar (default) |
| Right | Collections in the right sidebar, always visible when sidebar is open |
| Both | Collections appear in both sidebars simultaneously |
This enables radically different workspace layouts per theme. For example, a “Developer Workbench” theme could move Collections to the right sidebar, freeing the left sidebar for pinned favorites and system tools — while keeping the entry inspector visible during editing.
// Theme A: Traditional layout (Collections on left)
features: {
layoutRegions: {
collections: "left";
}
}
// Theme B: Developer workspace (Collections on right, compact density)
features: {
layoutRegions: {
collections: "right";
}
}
density: "compact";
High Contrast Mode and Reduced Motion are accessibility features. Enable them to meet WCAG 3.0 Functional Performance standards for users with visual or vestibular sensitivities.
Sticky Action Bar
When Sticky Action Bar is enabled, any page can make its action buttons stick to the bottom of the viewport on scroll. Wrap your buttons in the <StickyActions> component:
<script>
import StickyActions from "@components/ui/sticky-actions.svelte";
</script>
<StickyActions>
<button onclick={save} aria-keyshortcuts="Mod+S">Save Changes</button>
<button onclick={reset}>Reset</button>
</StickyActions>
The buttons render both in their original position AND in the sticky bar at the bottom of the screen — no duplication in your code. The bar uses role="toolbar", aria-label="Page actions", and aria-live="polite" for screen reader compatibility.
The sticky bar height adapts to density — 44px in compact, 56px in cozy, 64px in spacious. Toggle the feature per-theme in Design System → Features.
⌨️ Advanced Customization
Custom CSS
The Advanced tab provides a code editor where you can inject custom CSS into the admin panel.
/* Example: Change the active tab indicator */
[data-admin-theme] .active-tab-indicator {
border-color: oklch(65% 0.2 260deg);
border-width: 3px;
}
Custom CSS is sanitized server-side before storage. Dangerous constructs (url(), @import, javascript:, <script>, HTML tags) are automatically stripped.
Custom CSS can override theme settings. If your density or variant changes don’t appear to take effect, check your custom CSS for conflicting rules.
Palette studio (Design System → Palette & Import)
A lightweight brand editor (Skeleton-inspired) lives on the Palette & Import tab:
| Control | Purpose |
|---|---|
| Primary / Tertiary / Surface | Core seeds (color picker + hex) |
| More colors | Secondary, success, warning, error |
| Live apply | Pushes expanded shades into theme CSS immediately |
| Expanded shades | Shows 50 / 500 / 950 previews |
| Apply / Reset / Clear | Commit, restore Corporate defaults, or remove only the palette CSS block |
Seeds expand via color-mix into full scales and rebind --admin-bg-* (same path as theme JSON import). Manual Advanced → Custom CSS outside the sveltycms-palette-start/end markers is preserved. Click Save Theme to persist.
For a full visual generator (typography, edges, etc.), use Skeleton Theme Create and import the JSON below.
Export / Import
- Export Theme JSON: Downloads the current theme (density, variant, features, custom CSS) as a portable JSON file.
- Import: Paste SveltyCMS theme JSON or Skeleton.dev theme exports in the Palette & Import tab.
Default install (Corporate workspace)
Fresh installs seed the active theme from /src/themes/default.json:
- Layout: cozy density, bordered cards, sticky action bar, collapsible sidebar
- Branding:
brandedLogin: true— login shows tenant site name + accent colors immediately - Palette: teal primary (
#0f766e), blue tertiary (#1d4ed8), warm professional tones
Users land on a polished default — no need to discover Design System first. Customize from Config → Design System when ready.
Skeleton.dev color import
Skeleton’s theme generator exports JSON with a properties map (--color-primary-500, --color-surface-950, etc.). SveltyCMS also accepts a shorthand palette format in /src/themes/*.json for quick branding:
"properties": {
"primary": "#0f766e",
"tertiary": "#1d4ed8",
"surface": "#f8fafc"
}
Shorthand keys expand to full 50–950 shade scales via CSS color-mix() — light surface values anchor at --color-surface-50; other palettes anchor at -500.
On import or boot sync, SveltyCMS maps those tokens to runtime admin CSS scoped under .admin-theme-container and [data-admin-theme] — so utilities like bg-primary-500 and text-tertiary-500 update immediately across the admin panel.
| Skeleton palette | SveltyCMS palette | Notes |
|---|---|---|
primary |
primary |
Direct match |
secondary |
secondary |
Direct match |
tertiary |
tertiary |
Direct match |
accent |
tertiary |
Mapped when Skeleton uses accent naming |
success |
success |
Direct match |
warning |
warning |
Direct match |
error |
error |
Direct match |
surface |
surface |
Direct match |
Skeleton --radius-base and --radius-container also bridge to --admin-radius-* tokens. You do not need to edit src/app.css — compile-time defaults stay intact; imported colors apply at runtime via the theme’s customCss field (visible and editable in Advanced → Custom CSS).
Skeleton CSS exports ([data-theme='...'] { ... }) pasted as JSON with a css or code field are supported when a name is provided.
Contrast warnings: On Skeleton or shorthand palette import, SveltyCMS runs an advisory WCAG AA audit (body text, button labels, accent on surface). Failures appear as toast warnings — imports are not blocked.
My Overrides (per-user preferences)
All users can override density, card style, reduced motion, high contrast, and layout regions (sidebars, page header/footer) on Design System → My Overrides (/config/design-system?tab=overrides). User → Settings offers a compact density/card/a11y strip and links to the same deep link for full layout prefs. Use My Layout to set each region to visible, hidden, or theme default; click Use current layout to snapshot the panels as they are now. Sidebar toggles also auto-save to your profile after 2 seconds (non-admins store only differences from the tenant theme). Preferences apply immediately across the admin shell without a page reload (optimistic client overlay + app:user-prefs invalidation).
Theme Files (/src/themes/*.json)
Drop a .json file into the /src/themes/ directory and Vite auto-imports it to the database on next save. This is the File + DB hybrid — files are version-controlled distribution artifacts, DB is the runtime source of truth.
Theme files are Git-trackable, shareable between instances, and marketplace-ready. Copy a .json between servers and it works identically.
Example — Default Theme (/src/themes/default.json):
{
"name": "Corporate",
"description": "Enterprise default theme preset — cozy density, bordered cards.",
"density": "cozy",
"variant": "bordered",
"features": {
"stickyActionBar": true,
"collapsibleSidebar": true,
"brandedLogin": true,
"layoutRegions": { "collections": "left", "mediaGalleries": "left" }
},
"properties": {
"primary": "#0f766e",
"secondary": "#334155",
"tertiary": "#1d4ed8",
"success": "#16a34a",
"warning": "#d97706",
"error": "#dc2626",
"surface": "#f8fafc"
}
}
Example — Developer Workbench (custom theme, save to src/themes/developer-workbench.json):
{
"name": "Developer Workbench",
"description": "Compact density, flat cards, collections on the right.",
"density": "compact",
"variant": "flat",
"features": {
"stickyActionBar": false,
"collapsibleSidebar": true,
"layoutRegions": { "collections": "right", "mediaGalleries": "left" }
},
"layoutState": {
"leftSidebar": "full",
"rightSidebar": "full",
"pageheader": "hidden",
"pagefooter": "hidden",
"header": "hidden",
"footer": "hidden"
}
}
Example — Distraction-Free Writer (custom theme, save to src/themes/distraction-free-writer.json):
{
"name": "Distraction-Free Writer",
"description": "Spacious density, elevated cards, all chrome hidden.",
"density": "spacious",
"variant": "elevated",
"features": {
"stickyActionBar": true,
"reducedMotion": true,
"layoutRegions": { "collections": "left", "mediaGalleries": "left" }
},
"layoutState": {
"leftSidebar": "hidden",
"rightSidebar": "hidden",
"pageheader": "hidden",
"pagefooter": "hidden",
"header": "hidden",
"footer": "hidden"
}
}
Reset to Defaults
The Danger Zone in the Advanced tab resets all settings to factory defaults. This cannot be undone.
⚡ Live Preview
All changes in Design System are applied instantly via Svelte 5’s reactive $state. The entire admin panel behind the settings page reflects your changes in real time — before you click Save.
A sticky Save Bar appears at the bottom whenever there are unsaved changes:
- Discard — Revert all preview changes back to the last saved state.
- Save Theme — Persist all settings to the database.
🏗️ Flexible Admin Layout Shell
Beyond visual styling, SveltyCMS’s admin layout is built on a highly flexible shell where every UI region can be independently toggled, resized, and adapted per screen size. Each admin theme can express not just colors and spacing, but full layout structure.
Independently Togglable Regions
The +layout.svelte (src/routes/(app)/+layout.svelte) wraps every admin page with six independently toggleable regions, all content-injectable via Svelte 5 <Slot>:
| Region | ui.state key |
Slot Name | Purpose |
|---|---|---|---|
| Top Toolbar | header |
global-toolbar |
Theme-injectable global toolbar |
| Left Sidebar | leftSidebar |
(built-in) | Navigation: collections, media, user, settings |
| Right Sidebar | rightSidebar |
(built-in) | Entry inspector: save, status, schedule, metadata |
| Page Header | pageheader |
(built-in) | Collection breadcrumb + action buttons |
| Page Footer | pagefooter |
(built-in) | Entry metadata (created/updated timestamps) |
| Bottom Bar | footer |
global-footer |
Theme-injectable status bar |
Additionally, a sticky action bar region (Slot name="sticky-action-bar") appears at the bottom of the main content area when theme.features.stickyActionBar is enabled.
<!-- +layout.svelte — how regions work -->
{#if ui.state.header !== 'hidden'}
<header style="height: var(--admin-header-height, 32px);">
<Slot name="global-toolbar" /> <!-- ← Theme injects custom toolbar here -->
</header>
{/if}
<!-- Sticky action bar — theme-controlled -->
{#if theme.features.stickyActionBar}
<div class="sticky bottom-0 backdrop-blur-md"
style="min-height: var(--admin-sticky-bar-height, 56px);">
<Slot name="sticky-action-bar" />
</div>
{/if}
{#if ui.state.footer !== 'hidden'}
<footer>
<Slot name="global-footer" /> <!-- ← Theme injects status bar here -->
</footer>
{/if}
Each region is independently toggleable at runtime via the ui singleton store (src/stores/ui-store.svelte):
// Switch between layouts in real time
ui.toggle("leftSidebar", "hidden"); // Full-width content mode
ui.toggle("rightSidebar", "full"); // Show entry inspector
ui.toggle("pageheader", "hidden"); // Hide breadcrumbs
The ui-store is verified by unit tests (tests/unit/ui-store.test.ts) confirming toggle operations, singleton behavior, and screen-size-aware updateLayout() calls.
How Components Use Layout State
Left Sidebar (left-sidebar.svelte) — The main navigation adapts its layout based on ui.state.leftSidebar:
full→ Shows logo, collection tree, media folders, user avatar grid, and all footer iconshidden→ Invisible; a toggle button inheader-edit.sveltelets users reveal it- The sidebar also has its own internal
isSidebarFullstate for expand/collapse cycles
Right Sidebar (right-sidebar.svelte) — The entry inspector panel (rightSidebar) appears conditionally:
- Shown in
edit/createmodes when the user has write permissions - Contains Save, Status toggle, Clone, Delete, Schedule, and entry metadata
- Automatically hides in
viewmode or when user lacks write access
Page Header (header-edit.svelte) — The collection header bar adapts to multiple contexts:
- Shows collection name + icon with current mode (
edit/create) - Offers a “Show More” dropdown on mobile for compact actions (Status, Delete, Schedule, Clone)
- Provides translation status, save/cancel buttons, and sidebar toggle
- Visibility controlled by both
ui.state.pageheaderandui.state.leftSidebar
Page Footer (page-footer.svelte) — Shows entry-level metadata:
- Created/Updated timestamps with username attribution
- Only visible when
ui.state.pagefooter !== 'hidden'
Responsive Breakpoints
Layout visibility adapts to screen size via src/utils/screen-size.ts, which defines six breakpoints:
| Breakpoint | Min Width | Layout Behavior |
|---|---|---|
xs |
0px | Mobile — floating bottom nav, hidden sidebars |
sm |
640px | Small tablet — compact sidebar, stacked content |
md |
768px | Tablet — full sidebar possible |
lg |
1024px | Desktop — full layout, all regions available |
xl |
1280px | Large desktop — spacious density comfortable |
2xl |
1536px | Ultrawide — maximum content area |
The screen store (src/stores/screen-size-store.svelte) exposes isDesktop and isMobile booleans. On mobile, a <FloatingNav /> replaces the sidebar, and the main content area gets extra bottom padding for the mobile navigation bar.
How Themes Leverage the Shell
Because every region is independently toggleable via ui.state, combined with the theme system’s density presets and --admin-sidebar-width CSS variable, theme authors have complete control over both visual appearance and structural layout:
// A "Full-Width Editor" theme: hide all chrome for maximum content canvas
ui.toggle("leftSidebar", "hidden");
ui.toggle("rightSidebar", "hidden");
ui.toggle("pageheader", "hidden");
ui.toggle("pagefooter", "hidden");
// A "Developer Workbench" theme: compact density + visible inspector + system footer
ui.toggle("rightSidebar", "full");
ui.toggle("pagefooter", "full");
// Plus: density = "compact", variant = "flat"
flowchart LR
subgraph Shell["Admin Layout Shell (6 regions + sticky bar)"]
direction TB
TH["Top Toolbar (header)<br/>Slot: global-toolbar"]
LS["Left Sidebar<br/>Collections + Nav"]
MC["Main Content<br/>+ Page Header/Footer"]
RS["Right Sidebar<br/>Entry Inspector"]
SB["Sticky Action Bar<br/>Slot: sticky-action-bar<br/>(theme-controlled)"]
BF["Bottom Bar (footer)<br/>Slot: global-footer"]
end
UI["ui-store<br/>6 toggleable regions"]
SC["Screen Size Store<br/>6 breakpoints"]
TM["Theme Context<br/>Density + CSS vars + layoutRegions"]
UI -->|"visibility"| Shell
SC -->|"responsive"| Shell
TM -->|"sizes + styles + layout"| Shell
This architecture provides a highly optimized toolbar, sidebar, and layout system — all without PHP/Twig overhead, using native Svelte 5 reactivity.
🧱 Architecture
The admin theme system is built on three pillars that work together:
1. Structural CSS Variables (--admin-*)
Defined in src/utilities.css, these variables control radii, shadows, sidebar width, header height, and density at the browser level. They cascade from the data-density attribute on the admin layout root element.
/* Applied automatically when density changes */
[data-density="compact"] {
--admin-sidebar-width: 200px;
--admin-header-height: 48px;
}
2. Semantic surface roles (--admin-bg-*, text, borders)
Also defined in src/utilities.css (light defaults + html.dark elevation). Use these instead of hardcoding paired dark: surface classes on admin shells:
| Token | Role |
|---|---|
--admin-bg-page |
Admin page canvas (AdminPageShell) |
--admin-bg-card |
Elevated card surface (AdminCard, .card) — one step above page in dark mode |
--admin-bg-sidebar |
Sidebar background |
--admin-border-default / --admin-border-subtle |
Hairline borders |
--admin-text-body / --admin-text-muted |
Primary / secondary text |
Card shadows stay variant-driven (flat / bordered / elevated) via AdminTheme.cardShadow and --admin-shadow-* — do not hardcode hover:shadow-lg on every card.
3. Svelte 5 AdminTheme Context
The AdminTheme class in src/components/ui/theme-context.svelte.ts is the reactive runtime state consumed by all 37+ native UI components (Button, Card, Input, Table, Modal, Drawer, etc.).
// Every component reads theme context for adaptive rendering
const theme = getThemeContext();
const shadow = theme?.cardShadow ?? "var(--admin-shadow-elevation)";
sequenceDiagram
participant SP as Settings Page
participant TC as Theme Context
participant CP as Components
SP->>TC: Mutate density/variant
TC-->>CP: Reactive $state update
CP->>CP: Re-render with new vars
Note over SP,CP: Instant — before save
SP->>API: POST /api/theme/admin-theme
API->>DB: Persist to Theme record
Theme Loading on Startup
App Start → hooks.server.ts → ThemeManager.getTheme()
→ reads config.adminTheme from DB
→ +layout.svelte sets AdminTheme context
→ all components render with stored settings
🔌 API Endpoints
All theme operations go through the REST API at /api/theme/. Admin authentication is required for write operations.
| Endpoint | Method | Description |
|---|---|---|
/api/theme/list |
GET | List all themes with summaries |
/api/theme/admin-theme |
GET | Get active theme config |
/api/theme/admin-theme |
POST | Save theme config |
/api/theme/admin-theme |
DELETE | Reset to defaults |
/api/theme/create |
POST | Create new theme ({ name }) |
/api/theme/activate |
POST | Activate theme ({ themeId }) |
/api/theme/clone |
POST | Clone theme ({ sourceId, name }) |
/api/theme/delete |
POST | Delete theme ({ themeId }) |
/api/theme/import-preset |
POST | Import Skeleton.dev/SveltyCMS JSON |
Related Documents
🛠️ Developer Guide: Building Custom Admin Themes
SveltyCMS’s admin theme system is designed for both GUI configuration and programmatic theme development.
Quick Start: Theme JSON File
Drop a .json file into /src/themes/ and Vite auto-imports it:
{
"name": "My Custom Theme",
"description": "A custom admin theme.",
"density": "cozy",
"variant": "bordered",
"features": {
"stickyActionBar": true,
"collapsibleSidebar": false,
"brandedLogin": false,
"highContrastMode": false,
"reducedMotion": false,
"layoutRegions": {
"collections": "left",
"mediaGalleries": "left"
}
},
"layoutState": {
"leftSidebar": "full",
"rightSidebar": "hidden",
"pageheader": "hidden",
"pagefooter": "hidden",
"header": "hidden",
"footer": "hidden"
},
"customCss": "/* Optional custom CSS */"
}
Programmatic Theme Creation
// Create via API
await fetch("/api/theme/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "My Theme",
settings: { density: "compact", variant: "flat" },
}),
});
// Activate a theme
await fetch("/api/theme/activate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ themeId: "..." }),
});
Reading Theme Context in Components
All 37+ native UI components already consume the AdminTheme context. Any custom component can too:
import { getThemeContext } from "@components/ui/theme-context.svelte";
const theme = getThemeContext();
// theme?.density → "compact" | "cozy" | "spacious"
// theme?.variant → "flat" | "bordered" | "elevated"
// theme?.cardShadow → synthesized shadow based on variant
// theme?.cardBorder → border width based on variant
// theme?.features?.stickyActionBar → boolean
Injecting Content into Layout Shell Regions
Three theme-injectable slots are available for plugin or theme content:
| Slot Name | Region | Purpose |
|---|---|---|
global-toolbar |
Top header | Global navigation toolbar |
global-footer |
Bottom bar | System status, version info |
sticky-action-bar |
Main content bottom | Page action buttons (via <StickyActions> component) |
Plugins register into these slots via the PluginSlot system (src/plugins/types.ts).
6-Region Layout Control
Every admin page lives inside a 6-region shell controlled by ui-store:
import { ui } from "@src/stores/ui-store.svelte";
// Toggle any region independently
ui.toggle("leftSidebar", "hidden");
ui.toggle("rightSidebar", "full");
ui.toggle("pageheader", "hidden");
// Layout state is persisted to DB per-theme automatically
// (debounced 2s save to config.adminTheme.layoutState)
Screen Size Adaptation
import { screen } from "@src/stores/screen-size-store.svelte";
// screen.isDesktop → true when width >= 1024px
// screen.isMobile → true when width < 768px