C CoolAdmin v3.3.0

Pug + SCSS pipeline

CoolAdmin's source pipeline — Pug templates with a single nav-data file, SCSS split into 56 partials across two stylesheets, and a Vite dev server with HMR. Built artifacts ship in the repo so end users skip the build.

Last updated May 22, 2026

CoolAdmin v3.2 introduced a Pug + SCSS source pipeline. The motivation: stop editing 35 HTML files when you want to add a sidebar item.

The pipeline is opt-in for contributors. End users still clone the repo and open index.html — the build artifacts ship committed.

What npm run dev boots

npm install     # one-time
npm run dev     # three concurrent watchers

Three processes run in parallel via concurrently:

ProcessWhat it watchesWhat it produces
pugsrc/pug/**/*.pug, src/pug/partials/content/*.htmlRoot *.html files
sasssrc/scss/**/*.scsscss/theme.css + css/app.css
viteBrowser-side HMR over http://localhost:3000(just a dev server, no bundling)

Edit any source — browser reloads in under a second.

npm run build runs the Pug + Sass compile once without watchers. There is no production bundling step beyond that.

The Pug source tree

src/pug/
├── layouts/
│   ├── _default.pug             # Dashboard layout (sidebar + topbar + main)
│   ├── _auth.pug                # Centered single-column (login, register, forgot password)
│   └── _error.pug               # 404 / 500 / maintenance (no sidebar, no topbar)
├── partials/
│   ├── _head.pug                # +head(pageMeta) mixin — emits the entire <head>
│   ├── _nav-data.pug            # 🌟 SINGLE SOURCE OF TRUTH FOR THE NAV
│   ├── sidebar.pug              # Desktop sidebar (reads _nav-data)
│   ├── header-desktop.pug       # Topbar (search, dropdowns, account menu)
│   ├── header-mobile.pug        # Mobile drawer (reads _nav-data)
│   ├── footer-scripts.pug       # Common <script> stack
│   └── content/
│       └── <page>.html          # Per-page inner markup as raw HTML
└── pages/                       # One file per route (35 total)
    └── <page>.pug

The layouts and partials are pure templating — no data, no logic. Pages set metadata in block variables and bring in their content fragment via include ../partials/content/<page>.

Pages — three layouts, one anatomy

Every page in src/pug/pages/ follows the same minimal shape:

extends ../layouts/_default

block variables
  - var pageMeta = {
  -   title: 'Operations dashboard | CoolAdmin',
  -   description: 'KPIs, sparklines, revenue chart, and activity feed.'
  - }
  - var activePage = 'dashboard'

block content
  include ../partials/content/index.html

Three things:

  • Which layout_default for dashboards, _auth for login/register/forget-pass, _error for 404/500/maintenance.
  • pageMeta — title and description used by the +head mixin to emit <title>, <meta name="description">, Open Graph tags, and Twitter Card tags. The auth layout adds noindex automatically.
  • activePage — string matching a key in _nav-data.pug. The sidebar highlights the corresponding item.

Available blocks

BlockWhereUse for
variablesall layoutspageMeta, activePage, bodyClass, skipLinkText
contentall layoutsthe inner page markup
extra_headallextra <link> / <style> in <head> (e.g. Leaflet CSS)
vendor_scriptsdefault, authvendor <script> BEFORE main JS (Chart.js, FullCalendar, Leaflet)
post_maindefaultcontent as sibling of <main> (e.g. Bootstrap modal definitions)
extra_scriptsallinline <script> blocks loaded LAST, after main JS

⚠️ Pug block is replace, not append. When you override block variables in a page, the layout’s defaults aren’t merged in — you must restate pageMeta, activePage, etc. for every page. That’s why every page in src/pug/pages/ repeats those declarations.

The single source of truth — _nav-data.pug

This is the file that justifies the pipeline:

//- Edit this file to add, rename, or reorder navigation items.

-
  var navItems = [
    {
      icon: 'fa-solid fa-tachometer-alt',
      label: 'Dashboard',
      children: [
        { label: 'Dashboard 1', href: 'index.html' },
        { label: 'Dashboard 2', href: 'index2.html' },
        { label: 'Dashboard 3', href: 'index3.html' },
        { label: 'Dashboard 4', href: 'index4.html' }
      ]
    },
    {
      icon: 'fa-solid fa-chart-bar',
      label: 'Charts',
      href: 'chart.html'
    },
    { icon: 'fa-solid fa-calendar-alt',    label: 'Calendar', href: 'calendar.html' },
    { icon: 'fa-solid fa-inbox',           label: 'Inbox',    href: 'inbox.html' },
    // ...
  ]

Edit the array, run npm run build:pug, every page rebuilds with the new menu. Both the desktop sidebar and the mobile drawer read from this same array — no duplication.

Item fields:

  • icon — Font Awesome 7 class (fa-solid fa-* or fa-regular fa-*)
  • label — display text
  • href — URL (omit on parent items that only have children)
  • children — array of { label, href } for a collapsible submenu

The sidebar highlights items by matching each href against the page’s activePage local.

Adding a new page

Two ways: via the migration helper, or by hand.

With the helper

node scripts/migrate-page.js my-new-page

The helper extracts the inner content and inline scripts from my-new-page.html (at the repo root) and creates:

  • src/pug/pages/my-new-page.pug
  • src/pug/partials/content/my-new-page.html

It detects which layout you need (default / auth / error) by looking at the source’s structure. Used during v3.2’s full migration of all 35 pages — useful when you copy a page from elsewhere and want to bring it into the pipeline.

By hand

# 1. Create the Pug page
echo 'extends ../layouts/_default

block variables
  - var pageMeta = { title: "Reports | CoolAdmin", description: "Q2 reports" }
  - var activePage = "reports"

block content
  include ../partials/content/reports.html
' > src/pug/pages/reports.pug

# 2. Drop the inner content
echo '<h1>Reports</h1>' > src/pug/partials/content/reports.html

# 3. Add the nav entry to _nav-data.pug
#    (edit by hand — append to navItems array)

# 4. Build
npm run build:pug

Restart the dev server if it’s running (Pug additions register at startup).

The SCSS source tree

Two entry files, each @use-ing a stack of partials in a deliberate order:

src/scss/
├── theme.scss                   # 20 ITCSS partials → css/theme.css (legacy stylesheet)
│   ├── _variables, _generic, _elements, _objects
│   ├── components/
│   │   ├── _buttons, _form, _header, _sidebar, _overview
│   │   ├── _cards, _charts, _tables, _footer
│   │   ├── _breadcrumb, _statistic, _progress, _alert, _switch
│   └── _utilities, _modern-additions
└── app.scss                     # 36 partials under app/ → css/app.css (modern overlay)
    ├── _variables, _overlay-base, _legacy-overrides
    ├── _topbar-layout, _responsive-sidebar, _dropdowns
    ├── _forms-bootstrap, _auth
    ├── _inbox, _inbox-pane, _kanban, _data-table, _invoice, _wizard
    ├── _card-showcase, _section-cards, _project-list, _rank-list, _pricing
    ├── _notifications, _empty-states, _toast
    ├── _command-palette, _theme-switcher, _theme-presets, _skeleton, _error-pages, _docs
    └── _mobile-refinements, _mobile-topbar, _mobile-topbar-final, _hamburger,
        _header-grid, _mobile-header-restore   (load order is load-bearing — see below)

@use order is load-bearing

The mobile-related app partials are deliberately layered:

@use 'app/mobile-refinements';
@use 'app/mobile-topbar';
@use 'app/mobile-topbar-final';
@use 'app/hamburger';
@use 'app/header-grid';
@use 'app/mobile-header-restore';

Each layer either overrides or restores something from the previous one. Don’t alphabetize this stack — the cascade is the spec. The naming (-final, -restore) is a deliberate signal that ordering matters.

Sass compiles both stacks back to byte-equivalent CSS that’s verified against the pre-refactor output via minified diff. If you refactor a partial, compile + diff against the previous css/*.css to confirm nothing regressed.

End-user mode

For someone who just wants to use CoolAdmin as a starting point:

git clone https://github.com/puikinsh/CoolAdmin.git
cd CoolAdmin
open index.html

No Node, no build, no dist/. The HTML and CSS in the repo root are the deliverable. The Pug + SCSS pipeline never runs.

This split — committed build artifacts + source pipeline — is what lets the template serve both audiences without forcing one workflow on everyone.

See also

  • Architecture — the bigger-picture overview the pipeline fits into
  • Theming — what the SCSS variables actually do
  • Deployment — shipping the built artifacts