Theme Customization
jekcms ships a theme customizer at admin/theme-customize.php that reads the active theme's theme.json and renders a tabbed editing UI for every option the theme exposes. Changes save to the database per-site — the theme's files never get modified, so a theme update won't wipe your customizations.
Opening the customizer
Appearance → Customize opens the panel. The left sidebar shows tabs rendered from the active theme's customizer.tabs config; the right pane shows a live preview that reloads as you tweak values.
If the active theme has no customizer.tabs defined, the customizer still offers the built-in Custom CSS and Custom HTML tabs.
Tabs come from theme.json
A theme declares its customizer UI like this:
{
"customizer": {
"tabs": {
"branding": {
"label": "Branding",
"icon": "palette",
"fields": {
"accent": {
"type": "color",
"label": "Accent color",
"default": "#2563eb",
"css_var": "--color-accent"
},
"logo": {
"type": "url",
"label": "Logo URL",
"default": ""
}
}
}
}
}
}
Each tab has label, optional icon (from Lucide icon set), optional description, and a fields map.
Supported field types
| Type | What it renders | Stored as | |---|---|---| | color | Color picker with hex input | #rrggbb string | | font | Google Fonts dropdown + weight selector | {"family": "...", "weight": "..."} | | range | Slider — honors min, max, step, unit | Number | | toggle | Single checkbox / switch | Boolean | | select | Dropdown from options | Option key | | textarea | Multi-line text input | String | | code | Monospace textarea with syntax highlighting | String | | url | URL input with validation | String | | text | Single-line text input | String |
Reading values in templates
Theme templates read customizer values with theme_option('tab.field'):
<?php $accent = theme_option('branding.accent'); ?>
<a href="..." style="color: <?= htmlspecialchars($accent) ?>">Link</a>
The key is {tab_key}.{field_key}. If the user hasn't set a value, the field's default from theme.json is returned.
Automatic CSS variable emission
Most of the time you don't need theme_option() — you just need CSS. Any field with a css_var mapping is auto-emitted as a :root variable.
Call output_theme_customization_css() once in your <head>:
<head>
<!-- ... -->
<?php output_theme_customization_css(); ?>
</head>
This generates:
<style id="theme-customization">
:root {
--color-accent: #2563eb;
--font-body: 'Inter', sans-serif;
--layout-max-width: 1200px;
}
</style>
Then in your stylesheet, reference the vars:
.btn-primary { background: var(--color-accent); }
User changes the accent color → the inline <style> block updates → the button repaints. No cache rebuild.
Example: overriding accent color
A visitor visits the site:
- Theme's
theme.jsonsetsbranding.accentdefault to#2563eb - Admin opens Customize → Branding → Accent color, picks
#dc2626 - Value is saved to the site's
theme_customizationJSON blob - On next render,
output_theme_customization_css()emits--color-accent: #dc2626 - Every
var(--color-accent)reference — buttons, links, focus rings — turns red
No file edits, no recompile.
Custom CSS
The built-in Advanced → Custom CSS tab lets you write arbitrary CSS that's appended after the theme's stylesheet. Useful for one-off tweaks without touching the theme folder.
/* Custom CSS tab */
.site-header { border-bottom: 2px solid var(--color-accent); }
Custom HTML injection
Advanced → Custom HTML exposes two slots:
- Head — injected before
</head>. Use for analytics snippets, verification meta tags, fonts. - Footer — injected before
</body>. Use for chat widgets, late-load scripts.
Both are rendered verbatim — jekcms doesn't sanitize. Only paste code you trust.
Dark mode compatibility
jekcms sets <html data-theme="dark"> when the user opts into dark mode. Write dark-mode overrides in your stylesheet:
:root { --color-bg: #ffffff; }
html[data-theme="dark"] { --color-bg: #0f172a; }
The customizer itself doesn't render a separate set of dark-mode fields — themes typically compute dark-mode values from the light-mode ones in CSS, or ship two css_var mappings under different names.