Skip to content

Documentation

Design System & Admin Theme Settings

Design System workspace: multi-theme management, density, visual styles, personal overrides, custom CSS, and live component preview.

10/11/2026
27 min read Edit on GitHub

SveltyCMS’s admin theme system has two layers that are easy to confuse:

  • Themes — named, workspace-wide designs (density, visual style, features, custom CSS) that an admin creates and activates. They apply to everyone and are stored in the database.
  • My Overrides — a per-user layer that sits on top of the active theme. It changes how the admin looks for you only; it never edits the theme and never affects other users.

This page (Config → Design System, /config/design-system) is the sole route — there is no separate Appearance page. Changes preview instantly before saving.

Deep links (query ?tab=):

Tab URL Audience Scope
My Overrides /config/design-system?tab=overrides All users (linked from /user Settings) Just you — layered on the active theme
Live Preview /config/design-system?tab=preview All users Read-only component catalog + token readout
Themes / Presets / Layout / Style / Features / Advanced ?tab=themes etc. Workspace admins Whole workspace (applies to every user)

🎨 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.

The built-in theme is a single row named Default. On boot (and on every /src/themes/*.json change) the file sync reconciles it: a legacy SveltyCMSTheme row is renamed to Default, any duplicate built-in row is pruned, and a stale customCss palette is cleared so brand colors always come from the src/app.css @theme tokens unless you explicitly set one.

Creating a Theme

  1. Navigate to Config → Design System → Themes.
  2. Enter a name (e.g., “Midnight Blue”, “High Contrast Reader”).
  3. Click Create.

New themes start with the current density, variant, and features as defaults. Switch to other tabs to customize before saving.

Tip

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.

Note

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";
Important

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 in the sticky bar at the bottom of the screen when the feature is on (and fall back to their original in-place position when it is off) — so the actions never appear twice on screen. The bar uses role="toolbar", aria-label="Page actions", and aria-live="polite" for screen reader compatibility.

Tip

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.

Caution

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 the default seeds, or remove only the palette CSS block

Seeds expand via a hue-preserving OKLCH ramp into full 50–950 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.

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 a shorthand palette block in the Palette & Import tab.

Default install

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: none — the builtin default carries no properties map, so brand colors come straight from the src/app.css @theme tokens (green primary). A stale palette here would silently override those tokens at :root.

Users land on a polished default — no need to discover Design System first. Customize from Config → Design System when ready.

Theme JSON import

Theme JSON exports carry 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": "#5fd317",
  "tertiary": "#1d4ed8",
  "surface": "#f8fafc"
}

Shorthand keys expand to full 50–950 shade scales via a hue-preserving OKLCH ramp — light surface values anchor at --color-surface-50, other palettes anchor at -500 — and every step is emitted as a CSS oklch(...) value.

On import or boot sync, SveltyCMS maps those tokens to runtime admin CSS scoped to :root (plus .admin-theme-container / [data-admin-theme]) — so every utility (bg-primary-500, text-tertiary-500), every --admin-* semantic role, and even portalled overlays update immediately, across the whole admin panel. Dark-mode overrides are emitted under html.dark so they keep the specificity edge over the light :root block.

Palette key SveltyCMS palette Notes
primary primary Direct match
secondary secondary Direct match
tertiary tertiary Direct match
accent tertiary Legacy alias for accent naming
success success Direct match
warning warning Direct match
error error Direct match
surface surface Direct match

--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).

CSS exports ([data-theme='...'] { ... }) pasted as JSON with a css or code field are supported when a name is provided.

Contrast warnings: On 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) {#my-overrides}

/config/design-system?tab=overrides — available to every signed-in user, no admin rights required.

This tab is not a theme editor. It is a thin per-user layer on top of the active workspace theme: it only changes how the admin looks for you, never the theme definition, and never other users. Everything applies immediately — no page reload.

Control Values What it changes (for your session only)
Density theme default · compact · cozy · spacious Panel spacing, sidebar width, text size
Card Style theme default · flat · bordered · elevated Card / panel variant
Reduced Motion on · off Disables animations and transitions
High Contrast on · off Boosts text / background contrast
My Layout (6 selects) theme default · visible · hidden Visibility of leftSidebar, rightSidebar, pageheader, pagefooter, header, footer

Use current layout snapshots the panels exactly as they are right now into the layout selects. You can also toggle sidebars directly in the shell — those auto-save after 2 seconds (non-admins store only the difference from the tenant theme).

Overrides vs. theme — the difference

Theme (Themes / Layout / Style / Features / Advanced tabs) My Overrides (this tab)
Scope Workspace-wide (every user) Just you
Who can edit Workspace admins only Every signed-in user
What it writes The theme definition stored in the database Your user.preferences.theme row
Route ?tab=themes, ?tab=layout, … ?tab=overrides
Affects other users Yes No

An override is always relative to the theme: leaving a select on Use theme default means “inherit whatever the active theme says”. Clear Overrides removes the whole personal layer and falls back to the theme.

Locking. If a workspace admin locks a setting in the theme (theme.lockedSettings), the matching control here is disabled. When layoutState is locked you see “Layout is locked by your workspace admin — tenant defaults apply.” Personal accessibility overrides (contrast, motion) still work unless the admin locked those specifically.

How it is saved. Save My Preferences calls PUT /api/user/update-user-attributes with { user_id: "self", newUserData: { preferences: { theme: … } } } — the server-side source of truth is user.preferences.theme. On success the UI caches the value in localStorage (sveltycms:theme-prefs), applies it through the userThemePrefs store (optimistic), re-renders the layout, and invalidates app:user-prefs to re-sync server data. Clear Overrides sends an empty payload for the same path.

Note

User → Settings offers a compact density/card/accessibility strip that edits the same per-user preferences and links to this deep link for the full layout controls. It is the same data, not a second system.

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.

Tip

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": "Default",
  "description": "SveltyCMS default theme — cozy density, bordered cards.",
  "density": "cozy",
  "variant": "bordered",
  "features": {
    "stickyActionBar": true,
    "collapsibleSidebar": true,
    "brandedLogin": true,
    "layoutRegions": { "collections": "left", "mediaGalleries": "left" }
  },
  "properties": {
    "primary": "#5fd317",
    "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 icons
  • hidden → Invisible; a toggle button in header-edit.svelte lets users reveal it
  • The sidebar also has its own internal isSidebarFull state for expand/collapse cycles

Right Sidebar (right-sidebar.svelte) — The entry inspector panel (rightSidebar) appears conditionally:

  • Shown in edit/create modes when the user has write permissions
  • Contains Save, Status toggle, Clone, Delete, Schedule, and entry metadata
  • Automatically hides in view mode 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.pageheader and ui.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

4. Motion & Transition System

All shared motion lives in src/utils/admin-transitions.ts and is reduced-motion safe (every transition drops to 0ms when prefers-reduced-motion: reduce or the theme’s reducedMotion feature is on — see the Accessibility Guide).

Transition When to use
adminPage Page entry (240ms fade + 8px rise) — applied once by (app)/+layout.svelte, keyed on page.url.pathname
adminFade Modal / section reveals (200ms fade)
adminStagger Card grids / list rows — pass index for a cascade
adminSlide Drawers / sidebars — signed distance for direction

Motion timing tokens live in src/utilities.css (--admin-motion-fast: 150ms, --admin-motion-base: 200ms, --admin-motion-slow: 300ms, --admin-ease-out, --admin-ease-in-out) and density radii come from --admin-radius-*. Use the shared utilities — never per-page in:fly/in:scale one-offs — so every route (dashboard, collection builder, system settings, setup wizard) moves identically.

Note

Neutral aliases: gray, slate, neutral, zinc, stone are aliased to the surface ramp in @theme (--color-gray-500: var(--color-surface-500)), so neutral utilities follow the active theme. Alpha on palette tokens is applied with color-mix(in srgb, var(--color-primary-500) 30%, transparent) — never rgb(var(--color-*) / 0.3), which is invalid once tokens resolve to oklch()/hex.

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/. Read methods require system:read, write methods require system:settings (fail-closed via ENDPOINT_PERMISSIONS); GET /api/theme/public is standalone and unauthenticated. Full payloads, response shapes and the public-branding contract live in the Theme & Design System API reference.

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/set-default POST Set system default ({ themeId })
/api/theme/import-preset POST Import SveltyCMS/shorthand JSON
/api/theme/get-current-theme GET Active theme record (404 if none)
/api/theme/update-theme POST Low-level customCss-only update
/api/theme/public GET Public tenant branding

🏢 Agency Whitelabeling & Client Handover

SveltyCMS is built from the ground up to support complete agency whitelabeling. Agencies and enterprise dev teams can rebrand the entire CMS administration experience in minutes, presenting a cohesive, professional brand identity to clients without touching a single line of CMS core code.

5-Minute Client Whitelabel Blueprint

Follow this step-by-step workflow to prepare a turnkey client installation:

flowchart LR A["1. Brand Identity
(Logo, Favicon, Site Name)"] --> B["2. Palette Studio
(Brand Hex Colors)"] B --> C["3. Branded Login
(Custom Login Experience)"] C --> D["4. Agency Credits
(Support Links in Footer)"] D --> E["5. Git Export
(/src/themes/client.json)"]

Step 1: Set Brand Identity & Assets

Navigate to Config → System Settings:

Setting Key Value / Format Effect
SITE_NAME Client Organization / Project Name Replaces “SveltyCMS” in browser title, header breadcrumbs, and sidebar. Capped at 40 characters so the sidebar lockup can never overflow (longer names are ellipsised).
SITE_SLOGAN Optional tagline (max 60 chars) Tagline rendered under the logo on the login screen. Leave blank to keep the translated default (with Sveltekit Power).
LOGO_URL /media/client-logo.svg or CDN Client brand logo rendered on login screen and admin sidebar
FAVICON_URL /favicon.ico or PNG Custom browser tab icon
ACCENT_COLOR Brand hex (e.g., #5fd317) Meta theme-color for mobile browser chrome

Step 2: Configure Client Brand Palette

Navigate to Config → Design System → Palette & Import:

  1. In the Palette Studio, input the client’s corporate primary color (e.g., #1e40af for corporate navy, #047857 for emerald).
  2. Choose a matching secondary and surface seed.
  3. SveltyCMS instantly generates full 50–950 tint and shade scales via a hue-preserving OKLCH ramp, automatically binding semantic variables (--admin-bg-*, --admin-border-*).
  4. Click Live apply to verify the aesthetic in real time across the current admin workspace.

Step 3: Enable Branded Login

In Config → Design System → Features:

  • Toggle Branded Login to ON.
  • When active, the /login authentication screen automatically:
    • Replaces default CMS logos with the client’s high-resolution LOGO_URL.
    • Prominently displays the custom SITE_NAME.
    • Uses the active theme’s card variant (flat, bordered, or elevated) and border styling.
    • Applies sanitized custom CSS rules scoped exclusively to the login container.

Step 4: Add Agency Support Credits & Contact Information

To provide a branded client handoff with direct support escalation, inject your agency credentials into the global-footer slot:

<!-- Example: Injecting agency support into layout via plugin or theme component -->
<div class="flex items-center justify-between text-xs text-surface-500 px-4 py-2">
  <span>© 2026 Acme Corp. All rights reserved.</span>
  <span>
    Managed by <a href="https://youragency.com" class="text-primary-500 font-medium hover:underline" target="_blank">YourAgency</a>
    • <a href="mailto:support@youragency.com" class="hover:underline">Need Help?</a>
  </span>
</div>

Step 5: Version-Control the Client Theme

Once customized, click Export Theme JSON on the Palette & Import tab. Save the output to:

/src/themes/client-brand.json

Because SveltyCMS features a File + DB Hybrid Theme Architecture, committing this file to your project repository guarantees that when your deployment pipeline runs (git push origin main), the client’s custom branding is automatically loaded into the database during initial setup or migration without manual GUI re-configuration.


Protecting Brand Integrity: Role Locking & Permissions

Clients and non-technical content editors should enjoy the customized interface without the risk of accidental brand breakage:

  1. Role Gating: Only users with the admin role or explicit theme:write permissions have access to /config/design-system.
  2. Editor Experience: Content editors and authors see the client’s branded interface out of the box, with full focus on content creation.
  3. Accessibility Overrides: Individual users can still adjust their personal preferences (such as High Contrast Mode or Compact Density) via /config/design-system?tab=overrides without impacting the global company branding.

💼 Commercial Strategy & Marketplace Ecosystem

SveltyCMS balances rapid open-source developer adoption with a sustainable commercial monetization path.

The “Redis vs. Memory Cache” Architecture Rule

To maintain peak performance and avoid bloated core code, SveltyCMS strictly enforces the Zero-Friction Core vs. Scalable Extensions design pattern:

Category Free Open-Core (Zero Dependencies) Commercial / Marketplace Extension
Caching High-performance in-memory LRU cache with sub-1µs hits Redis L2 Distributed Cluster with pub/sub invalidation
Database Embedded SQLite with sub-2ms local persistence Managed PostgreSQL / MariaDB / MongoDB with connection pooling
Media Local filesystem storage with WebP/AVIF processing S3 / Cloudflare R2 Cloud Storage CDN with global edge cache
Theme System Built-in 6-region shell, Palette Studio, 37+ UI components Premium Agency Theme Packs (Medical, SaaS, Editorial)
Translations Standard multi-locale content schema & analytics engine AI Automated Bulk Translation (DeepL / OpenAI cockpit plugin)
Security Native WebAuthn passkeys, 2FA TOTP, AES-256 ALE Enterprise SAML / OIDC Single Sign-On (SSO) connector
Tip

Adoption First: The Open-Core foundation contains everything required to build blazing-fast production websites. No arbitrary artificial limits are placed on user accounts, collections, API traffic, or languages.

Monetization Channels

  1. Agency Whitelabel Pro Packs:
    • Turnkey agency tools including multi-client workspace switchers, client billing integration, and one-click brand provisioning.
  2. Marketplace Extensions (marketplace.sveltycms.com):
    • Official and third-party verified widgets, dashboards, and enterprise connectors.
    • Transparent 70/30 developer revenue share, encouraging ecosystem developers to build and sell high-value Svelte 5 components.
    • Built-in license verification via src/utils/license-manager.ts supporting offline-safe validation and 14-day client evaluation periods.
  3. Managed SveltyCMS Cloud:
    • Turnkey global hosting with zero DevOps overhead, automated daily backups, managed Redis clusters, and edge CDN distribution.

🛠️ 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

📚 Related Documents

themeappearanceadmin-themedensitycustom-cssconfiguration
Was this page helpful?