Theme Structure
Only theme.json and css/style.css are required for a theme to be detected. All other files are optional — a missing template silently falls back to theme/default/, so your CSS loads against someone else's HTML until you supply your own version of that file.
A fully functional theme needs at minimum: theme.json, css/style.css, header.php, footer.php, and home.php.
theme/my-theme/
│
├── theme.json ← REQUIRED — manifest
├── functions.php ← Optional — theme-specific PHP helpers
│
├── css/
│ ├── style.css ← REQUIRED — must be named exactly style.css
│ ├── contact.css ← Optional — contact form styles
│ └── shortcodes.css ← Optional — CMS shortcode styles
│
├── js/
│ └── script.js ← Optional — must be named exactly script.js
│
├── header.php ← Page opening (<!DOCTYPE> → <main>)
├── footer.php ← Page closing (</main> → </html>)
├── home.php ← Homepage template
├── 404.php ← 404 error template
│
├── content-articles.php ← Single article view
├── content-projects.php ← Single project view
├── content-pages.php ← Single static page view
├── content-list.php ← List view (articles/projects/categories/tags)
│
├── partials/
│ ├── article-card.php ← Article card HTML override
│ └── project-card.php ← Project card HTML override
│
├── page-templates/ ← Custom page templates (selectable per page in admin)
│ ├── contact.php
│ └── landing.php
│
└── preview.jpg ← 350×350px screenshot for the theme manager
theme.json
{
"synaptik_theme": true,
"name": "My Theme",
"version": "1.0.0",
"description": "A minimal theme for SynaptikCMS.",
"author": "Your Name",
"author_url": "https://example.com",
"screenshot": "preview.jpg"
}
"synaptik_theme": true is required — it's how the CMS tells a real theme folder apart from anything else placed under /theme/.
Optional fields used only if you intend to distribute updates through the CMS's own update checker: download_url, info_url, demo_url (matching the pattern used by bundled themes like Ink or Vitae).
The theme's folder name — not a JSON field — becomes its identifier in config.json's active_theme. Never name it default; that folder belongs to the bundled fallback theme and isn't protected from being overwritten on a core update.
Asset naming conventions
| Asset | Required name | Path |
|---|---|---|
| Main CSS | style.css | theme/{name}/css/style.css |
| Theme JS | script.js | theme/{name}/js/script.js |
Both are detected and injected automatically by render_header_scripts(). Do not hardcode <link>/<script> tags for them in your templates — that produces duplicate loads.
Light/dark logo variants
If your theme has a dark/light mode toggle, render_site_logo() (called from your header.php) can swap the site logo automatically between modes — with no extra code in your theme.
It works purely from file naming: if the site's configured logo is files/logo.webp, and both files/logo-light.webp and files/logo-dark.webp also exist (uploaded via Admin → File Manager), both <img> tags are rendered and toggled with a small inline <style> block based on the [data-theme] attribute your toggle sets on <html>. Pure CSS, no flash, no theme code required. If either variant is missing, it silently falls back to rendering the single configured logo as before.
The only thing your theme needs to get this "for free" is to already set [data-theme="dark"] / [data-theme="light"] on <html> for its mode toggle — the same convention used by the bundled Atrium, Ink, Prism, Vitae, and Synaptik-Docs themes. If your theme uses a different attribute or a body class instead, this feature won't trigger (the fallback single-logo behavior still works fine).
See render_site_logo() for the full behavior.
Installing a theme
Manual: create theme/my-theme/ with theme.json, css/style.css, header.php, footer.php, home.php. It appears automatically in Admin → Appearance → Themes once theme.json is valid. Add the remaining templates progressively — the CMS falls back to theme/default/ for anything still missing.
ZIP upload: zip the theme folder so the ZIP contains the folder itself (not just the loose files inside it), then Admin → Appearance → Import Theme.
Pre-publish checklist
[ ] theme.json present with "synaptik_theme": true
[ ] css/style.css present — named exactly style.css
[ ] header.php, footer.php, home.php present
[ ] preview.jpg present (350×350px)
[ ] header.php calls render_header_scripts($headerScripts)
[ ] footer.php calls render_search_ui() before </body>
[ ] CSS defines .articles-grid, .article-card, .projects-grid, .project-card
[ ] All UI strings use __t()
[ ] Theme works without partials/ and without content-list.php (both optional)
[ ] Folder name is not "default"
[ ] Tested on mobile
