Templates
header.php
Generates all HTML from <!DOCTYPE html> to the opening of the main content area.
Injected variables
| Variable | Type | Description |
|---|---|---|
$settings | array | All site settings |
$data | array | Site content — see note below |
$pageTitle | string | Current page title |
$metaTitle | string | Calculated SEO <title> |
$metaDescription | string | Calculated SEO meta description |
$metaKeywords | string | Meta keywords, if set on the item |
$ogImage, $ogTitle, $ogDescription | string | Open Graph values |
$type | string | Current content type — article, project, page, '', or a site owner's custom type identifier (see Content Model Reference) |
$slug | string | Current item slug (empty on list pages) |
$headerScripts | array | Extra scripts to inject — pass to render_header_scripts() |
On list pages and the homepage, $data['article'] holds the full lightweight index. On single-item pages it holds only that one item. Use sl_load_index('article') whenever you need the full catalogue from an uncertain context.
Minimal <head>
<!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($settings['site_language'] ?? 'en'); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $metaTitle; ?></title>
<?php echo render_meta_tags($settings, $metaTitle, $metaDescription); ?>
<?php echo render_header_scripts($headerScripts); ?>
</head>
render_header_scripts() injects the <base> tag, i18n/base-URL JSON islands, front-boot.js, the (plain, blocking) system CSS bundle — including shortcodes.css/gallery-layout.css, each only when the page actually needs them — your theme's style.css, search.js (always), the RSS link, highlight.js (conditional), $headerScripts, your theme's script.js, and finally lightbox.css+lightbox.js/collapsibles.js (conditional, enqueued last) — see Rendering Functions for the exact order. Never link assets/css/style.css, assets/js/script.js, or any core asset (including lightbox.css) manually — every core asset is already injected for you, and hand-linking one loads it twice. There's no core-provided page-fade-transition script — if your theme wants a fade-in on load or a fade-out before a pagination link navigates, add the few lines directly to your own script.js (see Front-End Loading Pipeline); it isn't worth a dedicated always-loaded file most themes never use.
Admin bar compatibility
Synaptik CMS injects a 36px admin toolbar at the top of the viewport when an administrator is logged in. Two things your theme needs to handle:
1. Offset fixed navigation
The CMS sets --snk-adminbar-height to 36px on :root when the bar is active (0px otherwise via the var() fallback):
.site-header {
position: fixed;
top: var(--snk-adminbar-height, 0px);
}
A secondary fixed element below the header needs calc():
.docs-sidebar {
position: fixed;
top: calc(var(--snk-adminbar-height, 0px) + var(--header-h));
}
position: sticky elements need no adjustment — flow already accounts for it via body { padding-top }. Do not declare --snk-adminbar-height yourself; the fallback handles the default case.
2. Render order
If your nav uses backdrop-filter, it creates a stacking context that can render the admin bar behind it regardless of z-index. Call render_adminbar() as the first child of <body>, before any theme markup:
</head>
<body>
<?php render_adminbar(); ?>
<header class="site-header">…</header>
It's a no-op for non-admin visitors. Without backdrop-filter in your nav this call is optional (the CMS emits the bar automatically after the header template regardless), but calling it explicitly is recommended for every theme.
footer.php
| Variable | Type | Description |
|---|---|---|
$settings | array | Site settings |
$data | array | Site content |
$currentYear | string | date('Y') |
$baseUrl | string | Base URL with trailing slash |
</main>
</div>
<?php echo render_search_ui(); ?>
</body>
</html>
render_search_ui() must run before </body> — it powers the Ctrl+K overlay regardless of whether the search icon is visible.
home.php
Rendered between header.php and footer.php on the homepage. Injected: $data, $settings.
<?php
$articles = $data['article'] ?? [];
usort($articles, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? ''));
$recent = array_slice($articles, 0, 5);
?>
<h1><?php echo htmlspecialchars($settings['site_title']); ?></h1>
<?php foreach ($recent as $article): ?>
<?php echo render_article_card($article); ?>
<?php endforeach; ?>
content-articles.php, content-pages.php, content-projects.php, content-{type}s.php
Single-item views. Receive $item (the full item array) and, for a custom content type, $type as well. Every render_* helper below returns a string.
A custom content type with no dedicated content-{type}s.php in your theme renders through theme/default/content-generic.php instead — see Content Model Reference.
<article>
<?php echo render_featured_image($item); ?>
<?php echo render_content_title($item); ?>
<?php echo render_content_date($item); ?>
<?php echo render_content_category($item); ?>
<?php echo render_content_tags($item); ?>
<div class="prose">
<?php echo render_content_html($item['content'] ?? '', $item); ?>
</div>
</article>
Always use render_content_html($item['content'], $item) — never echo $item['content'] directly.
content-list.php
| Variable | Type | Description | |||||
|---|---|---|---|---|---|---|---|
$list_type | string | 'article' \ | 'project' \ | 'page' \ | a custom type's identifier \ | 'category' \ | 'tag' |
$articles | array | Articles to display, sorted by date desc | |||||
$projects | array | Projects to display, sorted by date desc | |||||
$items | array | All items with an _content_type key | |||||
$filter_value | string | Current category or tag slug | |||||
$data | array | Full data store | |||||
$settings | array | Site settings |
Page templates (page-templates/)
<?php /* Template Name: Contact */ ?>
<div class="contact-page">
<h1><?php echo htmlspecialchars($item['title']); ?></h1>
<?php echo render_content_html($item['content'] ?? '', $item); ?>
<?php echo render_contact_form_html(); ?>
</div>
Available variable: $item. Call loadConfig() if you need $settings here.
