# Theming
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](https://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.md
```
Place 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:
```bash
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](https://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:
```dockerfile
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.zip
```
> **Tip:** Run the theme download step *before* `mix assets.deploy` so that `phx.digest` fingerprints 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
```scss
// 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:
```scss
// 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:
```scss
@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](https://demo.pureadmin.io/tools/font-test) to find the right values for your font.
### Complete example
Audi theme with Monda font, baseline-corrected:
```scss
// 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-face` declarations (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](https://pureadmin.io/docs/theme-customization).
## Creating Custom Themes
Use the Pure Admin CLI to scaffold and publish your own themes:
```bash
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](https://pureadmin.io/docs/creating-themes) 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:
```heex
<.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:
```heex
<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:
```heex
<.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=cobalt2
```
The inline script in the root layout reads the query param, stores it in `localStorage`, and swaps the theme CSS link before paint.