Bolt CMS Docs
Sign in

Architecture

Layouts & Templates

How Bolt CMS composes pages from headers, layouts, and footers — and how to build your own custom ones.

Layout System Overview

Every page in Bolt CMS is assembled from three parts:

  1. Header — navigation, branding, and site-wide top bar.
  2. Layout — the structural template that wraps the page content.
  3. Footer — bottom links, copyright, and optional widgets.

The entry point (index.php) loads these in sequence after the page file has been required:

<body>
    <?php
        require_once(__DIR__ . '/headers/' . $page['config']['header'] . '.php');
        require_once(__DIR__ . '/layouts/' . $page['config']['layout'] . '.php');
        require_once(__DIR__ . '/footers/' . $page['config']['footer'] . '.php');
    ?>
</body>

Each page controls which header, layout, and footer it uses by setting values in $page['config']. If a page does not set these, Bolt falls back to its defaults — the default header, layout, and footer.

The canvas layout

Bolt ships a bare canvas layout that does nothing but echo your page content:

<?php echo $page['content']; ?>

A page that sets 'canvas' renders through it, so the page itself owns all of its structure — ideal for the homepage, landing pages, and any full-bleed design. The default layout is different: it is the documentation shell (a sidebar nav, a page heading, and prev/next links), and it is the layout a page gets when it sets none.

Everything beyond this is a custom layout: a PHP file you add to layouts/ that wraps $page['content'] in whatever structure you need. The boilerplates below are ready to drop in.

How to Select a Layout

Set the layout in your page file before the output buffer:

<?php

// Wrap this page in the "centered" custom layout
$page['config']['layout'] = 'centered';

ob_start();
?>

<!-- Page content here -->

<?php
$page['content'] = ob_get_clean();
?>

Headers, layouts, and footers are independent — you can mix and match any combination. Set a value to an empty string to skip that part entirely:

$page['config']['header'] = 'default';
$page['config']['layout'] = 'sidebar';
$page['config']['footer'] = '';   // render no footer

Creating a Custom Layout

A layout is just a PHP file in layouts/. It receives the full $page array in scope and is responsible for rendering $page['content']. The smallest useful custom layout simply wraps the content in a container:

<!-- layouts/custom.php -->
<div css="max-width: 64rem; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; padding-top: 3rem; padding-bottom: 3rem;">
    <?php echo $page['content']; ?>
</div>

Then reference it from any page:

$page['config']['layout'] = 'custom';

Your layout has access to all $page['config'] values, global constants like SITEURL and ROOT_DIR, and any variables defined in index.php such as $currentPath. The boilerplates below put these to work.

Custom Layout Boilerplates

Three ready-to-use layouts you can copy straight into layouts/ and start using immediately. Each is small enough to read in one sitting and tweak to taste. They style their markup with the Paradigm CSS css="" attribute — the same approach the rest of your pages use.

centered — single-column reading

A narrow, centered column for long-form prose: blog posts, guides, changelog entries, and legal pages. It renders an optional title header from pageTitle / pageDescription, then your content.

<!-- layouts/centered.php -->
<div css="max-width: 42rem; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; padding-top: 4rem; padding-bottom: 4rem;">
    <?php if (!empty($page['config']['pageTitle'])) : ?>
        <header css="margin-bottom: 2.5rem;">
            <h1 css="font-size: 2.25rem; font-weight: 700; letter-spacing: -0.025em; color: #0f172a;">
                <?php echo $page['config']['pageTitle']; ?>
            </h1>
            <?php if (!empty($page['config']['pageDescription'])) : ?>
                <p css="margin-top: 0.75rem; font-size: 1.125rem; color: #64748b;"><?php echo $page['config']['pageDescription']; ?></p>
            <?php endif; ?>
        </header>
    <?php endif; ?>

    <?php echo $page['content']; ?>
</div>

sidebar — two-column with navigation

A left navigation rail beside your content — ideal for documentation and knowledge bases. The nav is data-driven: define a nav array in the page config and the layout renders it, highlighting the current page using the global $currentPath.

<!-- layouts/sidebar.php -->
<?php $nav = $page['config']['nav'] ?? []; ?>
<div css="max-width: 80rem; margin-left: auto; margin-right: auto; display: block lg:flex; gap: 3rem; padding-left: 1.5rem; padding-right: 1.5rem; padding-top: 3rem; padding-bottom: 3rem;">
    <aside css="width: lg:15rem; flex-shrink: 0;">
        <nav css="display: flex; flex-direction: column; gap: 0.25rem;">
            <?php foreach ($nav as $item) :
                $isActive = ($currentPath === $item['href']);
            ?>
                <a href="<?php echo SITEURL . $item['href']; ?>"
                   css="display: block; border-radius: 0.375rem; padding-left: 0.75rem; padding-right: 0.75rem; padding-top: 0.5rem; padding-bottom: 0.5rem; font-size: 0.875rem; <?php echo $isActive
                       ? 'background: #f1f5f9; font-weight: 500; color: #0f172a;'
                       : 'color: #64748b #0f172a:hover;'; ?>">
                    <?php echo $item['label']; ?>
                </a>
            <?php endforeach; ?>
        </nav>
    </aside>

    <main css="flex: 1_1_0%; min-width: 0;">
        <?php if (!empty($page['config']['pageTitle'])) : ?>
            <h1 css="font-size: 1.875rem; font-weight: 700; letter-spacing: -0.025em; color: #0f172a; margin-bottom: 2rem;">
                <?php echo $page['config']['pageTitle']; ?>
            </h1>
        <?php endif; ?>
        <?php echo $page['content']; ?>
    </main>
</div>

Feed it a navigation list from any page that uses it:

$page['config']['layout'] = 'sidebar';
$page['config']['nav'] = [
    ['label' => 'Overview',      'href' => '/guide'],
    ['label' => 'Installation',  'href' => '/guide/install'],
    ['label' => 'Configuration', 'href' => '/guide/config'],
];

split — split-screen

A 50/50 split with your content on one side and a visual panel on the other. Useful for landing heroes, sign-in screens, and feature spotlights. The visual panel collapses on mobile, and an optional splitImage config value fills it with a background image.

<!-- layouts/split.php -->
<?php
$paneStyle = !empty($page['config']['splitImage'])
    ? "background-image: url('" . $page['config']['splitImage'] . "'); background-size: cover; background-position: center;"
    : '';
?>
<div css="display: block lg:grid; grid-template-columns: lg:repeat(2,minmax(0,1fr)); min-height: lg:calc(100vh-3.5rem);">
    <!-- Content pane -->
    <div css="display: flex; align-items: center; padding-left: 1.5rem lg:4rem; padding-right: 1.5rem lg:4rem; padding-top: 4rem; padding-bottom: 4rem;">
        <div css="width: 100%; max-width: 28rem; margin-left: auto; margin-right: auto;">
            <?php if (!empty($page['config']['pageTitle'])) : ?>
                <h1 css="font-size: 1.875rem; font-weight: 700; letter-spacing: -0.025em; color: #0f172a; margin-bottom: 1.5rem;">
                    <?php echo $page['config']['pageTitle']; ?>
                </h1>
            <?php endif; ?>
            <?php echo $page['content']; ?>
        </div>
    </div>

    <!-- Visual pane -->
    <div css="display: none lg:block; background: #0f172a;" style="<?php echo $paneStyle; ?>"></div>
</div>

Header System

Header files live in headers/ and are loaded by name. The built-in default header is a slim, sticky top bar with:

  • Bolt CMS logo and wordmark linking to the site root
  • A "Docs" badge link next to the logo
  • Mobile hamburger button that toggles the sidebar
  • Frosted-glass backdrop blur effect
  • Fixed height of 3.5rem (56px)

Footer System

Footer files live in footers/ and follow the same naming pattern. The built-in default footer is intentionally minimal — often empty on documentation pages, where the sidebar and prev/next navigation already give readers enough context.

Creating Custom Headers and Footers

Headers and footers work the same way as layouts — a PHP file loaded by name, with the $page array in scope. This is how you give different sections of a site different navigation or footers. Create a file in headers/ (or footers/) and echo whatever markup you need:

<!-- headers/minimal.php -->
<header css="border-bottom: 1px_solid_#e2e8f0;">
    <div css="max-width: 80rem; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; height: 3.5rem; display: flex; align-items: center;">
        <a href="<?php echo SITEURL; ?>/" css="font-weight: 600;">My Site</a>
    </div>
</header>

Select your header or footer per page, exactly like a layout:

$page['config']['header'] = 'minimal';
$page['config']['footer'] = 'minimal';

To render a page with no header or footer at all, set the value to an empty string:

$page['config']['header'] = '';
$page['config']['footer'] = '';
API routing Page types