folderweb/docs/02-tutorial/03-templates.md
Ruben 8855a9b5be Update getting started documentation
Remove redundant quick start section
Simplify requirements and installation
Clarify local development setup
Streamline first page creation
Add shared hosting deployment instructions
Update tutorial content structure
Improve content format explanations
Clarify asset handling
Simplify metadata documentation
Update styling documentation
Improve template explanations
Remove unnecessary examples
Make documentation more concise
2026-02-07 19:14:13 +01:00

7.9 KiB

Templates

Templates control how content is presented. FolderWeb uses plain PHP templates — HTML with embedded PHP for dynamic output.

Template Types

FolderWeb has three template levels:

Base Template (base.php)

The HTML scaffold wrapping every page. Contains <head>, navigation, footer, and a $content placeholder for the inner template.

Page Template (page.php)

Wraps single-page content. Receives the rendered content and metadata.

List Templates (list.php, list-grid.php, list-compact.php)

Displays collections of items from subdirectories. Receives an $items array and optional intro content.

Template Location

Templates live in custom/templates/:

custom/
└── templates/
    ├── base.php           # HTML scaffold
    ├── page.php           # Single page wrapper
    ├── list.php           # Default list layout
    ├── list-grid.php      # Grid card layout
    └── list-compact.php   # Compact list layout

FolderWeb falls back to app/default/templates/ for any template not present in custom/.

Base Template

The base template wraps every page. Here is a minimal example:

<!DOCTYPE html>
<html lang="<?= $currentLang ?? 'en' ?>">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><?= htmlspecialchars($pageTitle) ?></title>

  <?php if (isset($metaDescription)): ?>
    <meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
  <?php endif; ?>

  <?php if (isset($socialImageUrl)): ?>
    <meta property="og:image" content="<?= $socialImageUrl ?>">
  <?php endif; ?>

  <link rel="stylesheet" href="/custom/styles/base.css?v=<?= $cssHash ?? '' ?>">

  <?php if (isset($pageCssUrl)): ?>
    <link rel="stylesheet" href="<?= $pageCssUrl ?>?v=<?= $pageCssHash ?? '' ?>">
  <?php endif; ?>
</head>
<body>
  <header>
    <nav>
      <a href="<?= htmlspecialchars($langPrefix ?? '') ?>/">My Site</a>
      <ul>
        <?php foreach ($navigation as $item): ?>
          <li>
            <a href="<?= htmlspecialchars($item['url']) ?>">
              <?= htmlspecialchars($item['title']) ?>
            </a>
          </li>
        <?php endforeach; ?>
      </ul>
    </nav>
  </header>

  <main>
    <?= $content ?>
  </main>

  <footer>
    <p>&copy; <?= date('Y') ?> My Site</p>
  </footer>

  <?php if (!empty($pageJsUrl)): ?>
    <script defer src="<?= htmlspecialchars($pageJsUrl) ?>?v=<?= $pageJsHash ?? '' ?>"></script>
  <?php endif; ?>
</body>
</html>

Key points:

  • $content contains the rendered output from the page or list template
  • Always escape user-facing values with htmlspecialchars()
  • Check optional variables with isset() before using them

Page Template

The page template wraps individual page content:

<article>
  <?php if (isset($metadata['title'])): ?>
    <header>
      <h1><?= htmlspecialchars($metadata['title']) ?></h1>

      <?php if (isset($metadata['date']) && ($metadata['show_date'] ?? true)): ?>
        <time datetime="<?= $metadata['date'] ?>">
          <?= $metadata['formatted_date'] ?>
        </time>
      <?php endif; ?>
    </header>
  <?php endif; ?>

  <div class="content">
    <?= $content ?>
  </div>
</article>

List Template

List templates display items from subdirectories:

<?php if ($pageContent): ?>
  <div class="page-intro">
    <?= $pageContent ?>
  </div>
<?php endif; ?>

<div class="blog-list">
  <?php foreach ($items as $item): ?>
    <article>
      <?php if (isset($item['cover_image'])): ?>
        <a href="<?= $item['url'] ?>">
          <img src="<?= $item['cover_image'] ?>"
               alt="<?= htmlspecialchars($item['title']) ?>"
               loading="lazy">
        </a>
      <?php endif; ?>

      <h2>
        <a href="<?= $item['url'] ?>">
          <?= htmlspecialchars($item['title']) ?>
        </a>
      </h2>

      <?php if (isset($item['date'])): ?>
        <time datetime="<?= $item['date'] ?>">
          <?= $item['formatted_date'] ?? $item['date'] ?>
        </time>
      <?php endif; ?>

      <?php if (isset($item['summary'])): ?>
        <p><?= htmlspecialchars($item['summary']) ?></p>
      <?php endif; ?>
    </article>
  <?php endforeach; ?>
</div>

Choosing a List Template

Select which list template to use per directory in metadata.ini:

title = "Projects"

[settings]
page_template = "list-grid"

Built-in options:

  • list — vertical list (default)
  • list-grid — card grid
  • list-compact — minimal compact list

Creating Custom List Templates

Create a new file in custom/templates/. For example, a timeline layout:

custom/templates/list-timeline.php:

<?= $pageContent ?>

<div class="timeline">
  <?php
  $currentYear = null;
  foreach ($items as $item):
    $year = isset($item['date']) ? date('Y', strtotime($item['date'])) : null;
    if ($year && $year !== $currentYear):
      $currentYear = $year;
  ?>
    <div class="year-marker">
      <h3><?= $year ?></h3>
    </div>
  <?php endif; ?>

  <article class="timeline-item">
    <time><?= $item['formatted_date'] ?? '' ?></time>
    <h4><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h4>
    <?php if (isset($item['summary'])): ?>
      <p><?= htmlspecialchars($item['summary']) ?></p>
    <?php endif; ?>
  </article>
  <?php endforeach; ?>
</div>

Use it with:

[settings]
page_template = "list-timeline"

Template Variables

Base template

Variable Type Description
$content string Rendered page/list HTML
$pageTitle string Page title for <title>
$metaDescription string SEO description
$navigation array Menu items (title, url, order)
$homeLabel string Translated "Home" text
$currentLang string Current language code
$langPrefix string URL language prefix ("" or "/no")
$languageUrls array Links to other language versions
$translations array Translated UI strings
$cssHash string Cache-busting hash for base CSS
$pageCssUrl string Page-specific CSS URL (if exists)
$pageJsUrl string Page-specific JS URL (if exists)
$pageLoadTime float Page generation time

Page template

Variable Type Description
$content string Rendered HTML from content files
$metadata array Page metadata (title, date, etc.)

List template

Variable Type Description
$items array Items to display
$pageContent string Rendered intro content
$metadata array Directory metadata

Each item in $items:

Key Type Description
url string Full URL to item
title string Item title
summary string Short description
date string ISO date (YYYY-MM-DD)
formatted_date string Localized date string
cover_image string Cover image URL

See the Template Variables Reference for the complete list.

Best Practices

Escape output. Always use htmlspecialchars() for user-facing values. The $content variable is already rendered HTML and does not need escaping.

Check variables. Use isset() before accessing optional values:

<?php if (isset($metadata['summary'])): ?>
  <p><?= htmlspecialchars($metadata['summary']) ?></p>
<?php endif; ?>

Keep logic minimal. Templates should display data, not process it. Complex logic belongs in plugins.

Use semantic HTML. Prefer <article>, <header>, <nav>, <time> over generic <div> elements.

Next Steps