PureAdmin supports the Pure Admin theme system with dynamic theme switching, color variants, and light/dark modes.
Available Themes
| Theme | Package |
|---|---|
| Default | @keenmate/pure-admin-core |
| Audi | @keenmate/pure-admin-theme-audi |
| Corporate | @keenmate/pure-admin-theme-corporate |
| Dark | @keenmate/pure-admin-theme-dark |
| Express | @keenmate/pure-admin-theme-express |
| Minimal | @keenmate/pure-admin-theme-minimal |
Browse and preview all themes at pureadmin.io.
Installing Themes
Theme zips are self-contained — the compiled CSS in dist/ references fonts via relative paths (../assets/fonts/...), so extracting a zip preserves correct asset resolution without any path adjustments.
Each theme zip contains:
audi/
├── theme.json # metadata: colors, variants, modes, fonts, checksums
├── dist/
│ └── audi.css # compiled CSS (ready to use)
├── scss/
│ └── audi.scss # SCSS source (for customization, see below)
├── assets/
│ └── fonts/
│ ├── *.woff2 # bundled font files
│ └── ...
└── README.mdPlace extracted themes under priv/static/themes/ so Phoenix can serve them:
Option A: Pure Admin CLI (recommended)
Install the CLI globally and manage themes in your project:
npm install -g @keenmate/pureadmin
# Add themes to your project
pureadmin themes add audi dark express
# Check for updates and re-download changed themes
pureadmin update
The CLI tracks theme versions and content_sha checksums in a pure-admin.json config file. Only changed themes are re-downloaded. Themes are extracted to priv/static/themes/ by default.
Option B: Manual download
Download theme zips from pureadmin.io and extract them into priv/static/themes/.
Option C: Download in CI/CD (Dockerfile)
Fetch themes automatically during your Docker build using the bundle API. Pass a comma-separated list of theme names to the themes query parameter — the API returns a single zip with all requested themes:
ARG THEMES_URL=https://pureadmin.io/api/bundle?themes=audi,dark,express,corporate,minimal
RUN apt-get update && apt-get install -y --no-install-recommends curl unzip && rm -rf /var/lib/apt/lists/* \
&& mkdir -p priv/static/themes \
&& curl -fsSL -o /tmp/themes.zip "${THEMES_URL}" \
&& unzip -o /tmp/themes.zip -d priv/static/themes \
&& rm -f /tmp/themes.zipTip: Run the theme download step before
mix assets.deployso thatphx.digestfingerprints the theme files along with the rest of your static assets.
See the Dockerfile in the repo root for a complete working example.
Customizing Themes via SCSS
If you install themes via npm (@keenmate/pure-admin-theme-*), you can import their SCSS source into your project stylesheet and override variables before the import. All theme variables use !default, so your values take precedence:
Basic variable override
// assets/css/app.scss
// Override variables before importing the theme
$base-accent-color: #0066cc;
$card-border-radius: 8px;
// Import the theme — your overrides win
@import '@keenmate/pure-admin-theme-audi/src/scss/audi';Custom font
Every theme bundles its own font (e.g., Audi bundles Fira Sans Condensed). To use a different font, override $base-font-family and declare your @font-face before the theme import:
// assets/css/app.scss
// 1. Set your font family (overrides the theme's bundled font)
$base-font-family: 'Monda', Arial, sans-serif;
// 2. Declare @font-face with your font files
@font-face {
font-family: 'Monda';
font-weight: 400;
font-display: swap;
src: url('./fonts/monda-400.woff2') format('woff2');
}
@font-face {
font-family: 'Monda';
font-weight: 700;
font-display: swap;
src: url('./fonts/monda-700.woff2') format('woff2');
}
// 3. Import the theme
@import '@keenmate/pure-admin-theme-audi/src/scss/audi';Tip: Bundle font files locally in your project rather than loading from a CDN. The theme's bundled font is local and renders first — a remote font arriving later causes a visible flash (FOUT).
Font baseline correction
Different fonts have different vertical metrics. When you swap fonts, text may appear higher or lower within buttons, card headers, and other aligned components. Use ascent-override and descent-override in @font-face to correct this:
@font-face {
font-family: 'Monda';
font-weight: 400;
font-display: swap;
src: url('./fonts/monda-400.woff2') format('woff2');
ascent-override: 110%; // push glyphs up within the line box
descent-override: 20%; // reduce space below the baseline
}| Descriptor | Effect | Typical range |
|---|---|---|
ascent-override | Controls where glyphs sit vertically. Higher % = text moves up. | 85% – 120% |
descent-override | Controls space below the baseline. Lower % = less descender space. | 10% – 40% |
size-adjust | Scales the font without changing font-size. Affects width and height. | 90% – 115% |
Use the Font Tuning Tool to find the right values for your font.
Complete example
Audi theme with Monda font, baseline-corrected:
// assets/css/app.scss
$base-font-family: 'Monda', Arial, sans-serif;
@font-face {
font-family: 'Monda';
font-weight: 400;
font-display: swap;
src: url('./fonts/monda-400.woff2') format('woff2');
ascent-override: 110%;
descent-override: 20%;
}
@font-face {
font-family: 'Monda';
font-weight: 700;
font-display: swap;
src: url('./fonts/monda-700.woff2') format('woff2');
ascent-override: 110%;
descent-override: 20%;
}
// Import theme — uses Monda everywhere instead of Fira Sans Condensed
@import '@keenmate/pure-admin-theme-audi/src/scss/audi';The theme's original
@font-facedeclarations (e.g., Fira Sans Condensed) remain in the compiled CSS but are never used since nothing references that font family name. This adds a few KB of unused CSS but has no runtime impact.
For more details see the Theme Customization guide on pureadmin.io.
Creating Custom Themes
Use the Pure Admin CLI to scaffold and publish your own themes:
npm install -g @keenmate/pureadmin
# Scaffold a new theme project
pureadmin init my-theme "My Theme"
# Edit src/scss/my-theme.scss, then build and preview
pureadmin build
# Package with integrity checksums
pureadmin pack
# Publish to pureadmin.io (requires API key)
pureadmin publish --api-key YOUR_KEY
The CLI handles SCSS compilation, font bundling, SHA-256 checksums, and ZIP packaging. Published themes are immediately available via the API and CLI for other projects.
See the Creating Themes guide on pureadmin.io for full documentation.
Theme Color Slots (1-9)
Every theme defines 9 custom color slots. These are used by components via the theme_color attribute:
<.alert theme_color="3">Custom branded alert</.alert>
<.button theme_color="5">Custom button</.button>
<.callout theme_color="1">Custom callout</.callout>Components supporting theme_color: alert/1, button/1, callout/1, toast/1, card/1, table_card/1, input/1, select/1, textarea/1.
Light / Dark Mode
Mode is managed client-side via the settings panel. The fouc_prevention_script applies the stored mode before paint to prevent flashing:
<body>
<.fouc_prevention_script default_mode="auto" />
{@inner_content}
</body>default_mode controls the first-visit mode (before any user selection is stored). Accepts "light", "dark", or "auto" (follows OS prefers-color-scheme). Defaults to "light".
CSS classes applied to <body>: pa-mode-light or pa-mode-dark (auto resolves to one of these at runtime).
Theme CSS Variables
Override these CSS custom properties to customize the appearance:
Core Colors
| Variable | Description |
|---|---|
--accent-color | Primary accent for interactive elements |
--base-text-color | Default body text |
--base-bg-color | Page background |
--base-border-color | Default borders |
Semantic Colors
| Variable | Description |
|---|---|
--base-success-color | Success/positive states |
--base-warning-color | Warning/caution states |
--base-danger-color | Danger/error states |
--base-info-color | Informational states |
Layout
| Variable | Description |
|---|---|
--pa-header-bg | Navbar background |
--pa-sidebar-bg | Sidebar background |
--pa-sidebar-width | Sidebar width (default: 26rem) |
Theme Slots
| Variable | Description |
|---|---|
--base-color-1 through --base-color-9 | Custom color slots |
Settings Panel
Add the settings panel to your layout for runtime theme/layout customization:
<.settings_panel default_theme="audi" />The panel fetches available themes from /api/themes/manifests and populates the selector dynamically. All settings persist to localStorage:
- Theme selection
- Color variant (per theme)
- Light/dark mode
- Layout width (fluid, sm, md, lg, xl, 2xl)
- Sidebar behavior (hide, icon-collapse, resizable, sticky)
- Font size and family
- Compact mode, RTL mode
Dynamic Theme Switching
Use ?theme=name query parameter to switch themes:
https://your-app.com/?theme=dark
https://your-app.com/?theme=cobalt2The inline script in the root layout reads the query param, stores it in localStorage, and swaps the theme CSS link before paint.