Starting Point: The HTML Mockup

Every JekCMS theme starts as a static HTML/CSS mockup. You design your pages in pure HTML, get the layout and styling perfect, then convert to a dynamic PHP theme. This approach is faster than designing in PHP from scratch because you can iterate on the HTML without worrying about database queries or template logic.

A typical HTML mockup has: index.html (homepage), post.html (single post), category.html (category archive), and 404.html (error page). These map directly to JekCMS template files.

Template Hierarchy

themes/mytheme/
├── index.php          # Homepage
├── single.php         # Single post
├── category.php       # Category archive
├── tag.php            # Tag archive
├── author.php         # Author page
├── search.php         # Search results
├── 404.php            # Not found
├── templates/
│   ├── partials/
│   │   ├── header.php
│   │   ├── footer.php
│   │   ├── sidebar.php
│   │   ├── post-card.php
│   │   └── pagination.php
│   └── page-contact.php   # Custom page template
├── assets/
│   ├── css/style.css
│   ├── js/main.js
│   └── images/
└── theme.json         # Theme metadata

Converting HTML to PHP: Header

The HTML <head> section becomes templates/partials/header.php:

<!DOCTYPE html>
<html lang="<?= get_current_language() ?>" data-theme="<?= get_theme_mode() ?>">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Dynamic title -->
    <title><?= htmlspecialchars($pageTitle ?? get_setting('general', 'site_name')) ?></title>

    <!-- SEO meta tags -->
    <?php output_meta_tags(); ?>
    <?php output_og_tags(); ?>
    <?php output_hreflang_tags(); ?>

    <!-- CSS -->
    <link rel="stylesheet" href="<?= theme_url('/assets/css/style.css?v=' . CMS_VERSION) ?>">

    <!-- Analytics/Scripts -->
    <?php if (function_exists('output_head_scripts')) output_head_scripts(); ?>
</head>

Template Tags

JekCMS provides helper functions (template tags) that replace hardcoded HTML content:

  • get_setting('group', 'key') — Read site settings
  • get_posts($options) — Query posts with filters
  • get_categories() — List all categories
  • get_featured_image($post, 'medium') — Get thumbnail URL
  • site_url($path) — Generate absolute URL
  • theme_url($path) — URL relative to theme directory
  • format_date($date, 'j F Y') — Localized date formatting

Post Card Partial

The post card is used on the homepage, category pages, search results, and sidebar. Creating it as a partial means one file to maintain:

<!-- templates/partials/post-card.php -->
<article class="post-card">
    <?php $img = get_featured_image($post, 'medium'); if ($img): ?>
    <a href="<?= site_url('/blog/' . $post['slug']) ?>" class="card-image">
        <img src="<?= $img ?>" alt="<?= htmlspecialchars($post['title']) ?>"
             width="800" height="500" loading="lazy">
    </a>
    <?php endif; ?>
    <div class="card-content">
        <span class="card-category"><?= htmlspecialchars($post['category_name'] ?? '') ?></span>
        <h2><a href="<?= site_url('/blog/' . $post['slug']) ?>">
            <?= htmlspecialchars($post['title']) ?>
        </a></h2>
        <p><?= htmlspecialchars($post['excerpt'] ?? substr(strip_tags($post['content']), 0, 160)) ?></p>
        <time datetime="<?= $post['published_at'] ?>"><?= format_date($post['published_at']) ?></time>
    </div>
</article>

Dark Mode Support

Use CSS custom properties for all colors. Define light mode as default and dark mode in a [data-theme="dark"] selector. The theme toggle script saves preference to localStorage and applies it before page render to avoid flash.

Testing Your Theme

Before submitting a theme: run Lighthouse audit (target 90+ on all metrics), test keyboard navigation, verify all template tags output correct data, check responsive design at 375px/768px/1024px/1440px, and verify dark mode switches correctly.