Theming & Bootswatch

Child apps use a custom SCSS pipeline built on Bootstrap 5 and the Bootswatch theme format. The result is a compiled CSS file that replaces Bootstrap entirely — including all variable overrides, component styles, and glassmorphism effects.

Why Bootstrap 5 + Bootswatch

Building on Bootstrap 5 means the entire HTML structure comes from one of the most mature, well-documented component systems on the web. Every card, form, modal, navbar, and grid layout uses standard Bootstrap markup — which means any developer (or AI agent) already knows the patterns.

The Bootswatch integration exists to showcase portability. The theme selector isn't just a novelty — it proves that the application's UI is completely decoupled from any single visual style. Switch from the glassmorphism theme to Darkly, Slate, or Sandstone and the app still works perfectly. That portability is the point.

The included glassmorphism theme is a starting point and a reference, not a requirement. It demonstrates the full depth of what's possible: SCSS variable overrides, a glass mixin, dark/light mode, canvas backgrounds, and styled versions of every Bootstrap component. From there, an AI agent can build an entirely new Bootstrap theme from scratch — a new _variables.scss, a new _bootswatch.scss, compiled to a single CSS file that drops in and replaces the default. No HTML changes needed.

How the Theme Stack Works

Three layers combine to create the final appearance:

LayerFilePurpose
1. Admin baseadmin/assets/css/style.cssSidebar layout, navbar structure, collapse behavior, badge hide on collapse
2. Theme overridesadmin/assets/css/bs-theme-overrides.cssDark/light mode Bootstrap overrides for standard Bootswatch themes
3. App themeyour-app/assets/css/custom.cssYour compiled SCSS theme — replaces Bootstrap CSS entirely

The theme stylesheet is loaded with id="themeStylesheet", which allows theme.js to swap it at runtime when the user picks a different Bootswatch theme.

Bootswatch Theme Switching

The child-app includes a live theme selector that pulls themes from the Bootswatch API. Users can switch between:

Theme Selector HTML

<select class="form-select form-select-sm" id="themeSelector" style="width: auto;"
        data-custom-css="../assets/css/custom.css"
        data-has-custom="<?= has_custom_theme() ? '1' : '0' ?>"
        data-registered-themes='<?= get_registered_themes_json() ?>'>
    <option value="custom">Custom</option>
</select>

How theme.js Works

  1. Fetches https://bootswatch.com/api/5.json to populate the dropdown
  2. On selection, swaps #themeStylesheet href to the Bootswatch CDN URL
  3. For the "Custom" option, points back to custom.css
  4. Persists the choice in localStorage (key: wn_theme) and a cookie
  5. Toggles sidebar mode: glass (transparent, canvas visible) for custom theme, dark (solid bg) for Bootswatch themes

Canvas Background

The #bgCanvas element is a fixed full-viewport canvas behind all UI. When the glassmorphism theme is active, all components are semi-transparent so the canvas shows through. When a standard Bootswatch theme is selected, theme.js hides the canvas automatically.

<canvas id="bgCanvas" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none;"></canvas>

bg-canvas.js renders a gradient + grid pattern that responds to data-bs-theme (dark/light mode). Replace its draw() function with your own rendering — particle effects, animated gradients, game backgrounds, etc.

SCSS Theme Pipeline

The child-app compiles its theme from SCSS following the Bootswatch pattern:

assets/scss/ custom.scss ← entry point _variables.scss ← Bootstrap variable overrides (colors, fonts, spacing) _bootswatch.scss ← post-Bootstrap component overrides (glassmorphism) themes/ ← additional theme variants (optional)

custom.scss (Entry Point)

// 1. Your variable overrides
@import "variables";

// 2. Full Bootstrap (uses your overridden variables)
@import "bootstrap/scss/bootstrap";

// 3. Post-Bootstrap component overrides and extras
@import "bootswatch";

This is exactly how Bootswatch themes are structured. You can take any Bootswatch theme's _variables.scss and _bootswatch.scss, drop them in, and compile — instant theme.

_variables.scss

Override Bootstrap's default Sass variables. The reference theme sets:

// Colors — vivid accents on transparent backgrounds
$primary:       #6366f1; // indigo-500
$secondary:     #94a3b8; // slate-400
$success:       #22c55e; // green-500
$info:          #06b6d4; // cyan-500
$warning:       #eab308; // yellow-500
$danger:        #ef4444; // red-500

// Body — transparent so canvas shows through
$body-bg:       transparent;
$body-color:    rgba(255, 255, 255, 0.9);

// Cards — glass panels
$card-bg:             rgba(255, 255, 255, 0.08);
$card-border-color:   rgba(255, 255, 255, 0.12);

// Forms — glass inputs
$input-bg:                  rgba(255, 255, 255, 0.08);
$input-border-color:        rgba(255, 255, 255, 0.15);
$input-focus-border-color:  rgba(99, 102, 241, 0.5);

Full variable reference: node_modules/bootstrap/scss/_variables.scss

_bootswatch.scss (Glass Mixin)

The glassmorphism mixin is the core of the transparent theme:

@mixin glass($bg: rgba(255, 255, 255, 0.06), $blur: 12px) {
  background: $bg;
  backdrop-filter: blur($blur);
  -webkit-backdrop-filter: blur($blur);
}

// Applied to every component:
.card    { @include glass(rgba(255, 255, 255, 0.06), 12px); }
.navbar  { @include glass(rgba(15, 15, 35, 0.05), 3px); }
.modal   { @include glass(rgba(20, 20, 45, 0.9), 24px); }
.alert   { @include glass(rgba(255, 255, 255, 0.06), 10px); }

Dark/Light Mode

Every component has two style sets:

The color mode toggle (color-mode.js) sets data-bs-theme on <html> and persists to localStorage. The canvas bg-canvas.js also observes this attribute and redraws with appropriate colors.

Themed Components

The _bootswatch.scss file styles these components for both modes:

Build Commands

npm install              # Install Bootstrap + sass
npm run build:css        # One-time compile → assets/css/custom.css
npm run watch            # Watch for changes during development
npm run build:themes     # Compile additional themes in themes/

Creating Your Own Theme

To create a different visual style while keeping the same structure:

  1. Copy _variables.scss and change the color palette. Set $body-bg: transparent if you want canvas effects, or a solid color for a standard theme.
  2. Edit _bootswatch.scss — keep the glass() mixin or replace it with your own effects. The light mode overrides in the [data-bs-theme="light"] block must match.
  3. Run npm run build:css to compile to assets/css/custom.css.
  4. Test with the theme selector — switch between your custom theme and any Bootswatch theme to verify the switcher still works.

Shared Assets from Admin

Child apps reference core's CSS and JS via relative paths — never copy these files.

CSS (loaded in <head>)

<link rel="stylesheet" href="../../admin/assets/css/style.css">
<link rel="stylesheet" href="../../admin/assets/css/bs-theme-overrides.css">
<link rel="stylesheet" href="<?= h(get_app_theme_css_url()) ?>" id="themeStylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">

JS (loaded in footer, after Bootstrap)

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../admin/assets/js/bs-init.js"></script>      <!-- apiPost() AJAX helper -->
<script src="../../admin/assets/js/sidebar.js"></script>       <!-- sidebar collapse/expand -->
<script src="../../admin/assets/js/color-mode.js"></script>    <!-- dark/light mode toggle -->
<script src="../../admin/assets/js/notifications.js"></script> <!-- notification bell -->
<!-- Your app-specific JS: -->
<script src="../assets/js/theme.js"></script>                  <!-- Bootswatch switcher -->
<script src="../assets/js/bg-canvas.js"></script>              <!-- canvas background -->

Template Shell

The reference views/template.php provides a complete application shell with these regions: