Aurelia 2 Admin

June 19, 2026 · View on GitHub

An Aurelia 2 admin panel starter with a calm, minimal UI, Tailwind v4 styling, and a fully featured route map you can grow into a real product. Built so a new page is a one-line change and the pieces drop cleanly into an existing Aurelia 2 app.

Highlights

  • Aurelia 2 RC1 + Router, webpack, Tailwind v4
  • Single source of truth for navigation — routes and the sidebar are generated from one config (src/navigation.ts). No more keeping a route table and a menu in sync.
  • Lazy-loaded routes — every page is code-split and fetched on first visit
  • Active-link highlighting in the sidebar, driven by the router
  • Reusable UI components<app-icon>, <page-header>, <stat-card> (registered globally, usable anywhere with no <import>)
  • Centralised themingThemeService owns dark mode; toggle lives in the topbar and the settings page, always in sync
  • Responsive shell — collapsible sidebar drawer on mobile
  • Auth scaffold with localStorage session, login page, and a route guard
  • A 404 / not-found route with a catch-all fallback
  • 24 ready-made admin pages across Core, Team, Operations, and Workspace groups

Getting started

Requirements: latest Node and npm.

npm install
npm start          # dev server on http://localhost:9000
npm run build      # production build to dist/
npm run lint       # eslint + htmlhint
npm test           # jest (scaffolded; passes with no tests)

Default login

  • Email: admin@example.com
  • Password: password

Replace the hardcoded credentials in src/services/auth-service.ts with your auth provider.

Adding a page (one step)

Add an entry to the relevant group in src/navigation.ts:

{ path: 'invoices', title: 'Invoices', icon: 'billing', load: () => import('./routes/invoices') },

Then create src/routes/invoices.ts + src/routes/invoices.html. That single entry wires up:

  • the route (lazy-loaded, with the page title), and
  • the sidebar link (with icon and active-state highlighting).

Start the template with the shared header component:

<div class="page">
  <page-header heading="Invoices" subheading="Track and reconcile billing.">
    <button class="btn-primary">New invoice</button>
  </page-header>
  <!-- page content -->
</div>

Project structure

  • src/navigation.ts — the navigation tree; the single source of truth for routes + sidebar
  • src/app.ts — the root component: builds the route table, owns shell state (mobile drawer, theme)
  • src/app.html — the shell layout (sidebar, topbar, viewport)
  • src/app.css — theme tokens, component styles, layout utilities
  • src/components/ — shared, globally-registered UI components
    • app-icon — named inline-SVG icon registry (<app-icon name="users">)
    • page-header — standard page title + actions slot
    • stat-card — compact metric card
  • src/services/auth-service, theme-service
  • src/routes/ — one view-model + template per page

Components

All three are registered globally in src/main.ts, so use them in any template directly.

<app-icon name="dashboard"></app-icon>

<page-header heading="Users" subheading="Manage access and roles.">
  <button class="btn-primary">Add user</button>
</page-header>

<stat-card label="Active Users" value="1,234" delta="+12%" hint="Last 7 days"></stat-card>

To add an icon, drop a <svg case="my-icon" class="icon" ...> entry into src/components/app-icon.html and reference it by name.

Routing notes

  • Routes are derived from navigation.ts via buildRoutes() and wrapped in a NavigationStrategy so each page's chunk loads only when visited.
  • '' redirects to dashboard; unmatched paths fall through to a not-found page.
  • Route guarding lives in src/auth-hook.ts (a global @lifecycleHooks canLoad).

Theming

  • Global tokens (--surface, --stroke, --accent, type scales…) live at the top of src/app.css.
  • ThemeService (src/services/theme-service.ts) is the only place that toggles/persists dark mode. It applies the dark class to <html>, persists to localStorage, and falls back to the OS preference. The topbar toggle and the settings page both go through it.

Data integration

Every page uses placeholder arrays inside its view-model (e.g. src/routes/users.ts). To wire up a backend:

  1. Create a service in src/services/.
  2. Inject it with resolve() and load data in a route hook (canLoad/loading) or binding().
  3. Map your API models into the templates.

To gate pages by role or plan, filter navigation in src/app.ts's routerNavs getter, and/or add checks in src/auth-hook.ts for hard route enforcement.

Use as a drop-in for an existing Aurelia 2 app

  • Copy src/components/ and register them in your main.ts (Aurelia.register(...components)).
  • Copy src/services/theme-service.ts for dark mode.
  • Copy src/navigation.ts and adapt the groups; merge buildRoutes() into your @route config.
  • Lift src/app.html / src/app.css shell markup and tokens as needed.

Tailwind v4 setup

  • src/app.css uses @import "tailwindcss"; and @config "../tailwind.config.js";
  • webpack.config.js uses @tailwindcss/postcss

Environment

Optional .env / .env.development files are supported via dotenv-webpack.