Turning a static HTML design into a jekcms theme: the folder layout, the header template, a reusable post card, the core template functions and the checks to run before going live.
Starting Point: The HTML Design
The easy way to start a theme is to work in plain HTML and CSS first. Building the homepage, post page, archive and 404 page as static files and getting the look right before moving to PHP lets you change the design as much as you like without dealing with database queries.
These HTML pages map directly to theme templates: index.html becomes the homepage template, post.html the post template, category.html the archive template and 404.html the not-found page.
Folder Layout
All bundled themes use the same layout. The shortest route is to copy one of them and start from there:
themes/mytheme/
+-- theme.json # name, version, colours, fonts
+-- functions.php # theme-specific helpers
+-- screenshot.png
+-- templates/
| +-- header.php, footer.php
| +-- index.php # homepage
| +-- single.php # post
| +-- page.php # page
| +-- archive.php # category and tag archives
| +-- search.php, 404.php
+-- partials/
| +-- post-card.php, pagination.php, sidebar.php, comments.php
+-- sections/ # blocks arranged in the layout editor
+-- assets/css, assets/js
There are no separate files for category and tag pages; both open with archive.php. Repeated pieces live in partials/ and are called from a template with get_template_part('post-card').
The Header Template
The <head> section of the HTML moves into templates/header.php. You do not write SEO tags, Open Graph, structured data or analytics code by hand; core functions print them. A shortened example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($pageTitle ?? get_setting('general', 'site_name')) ?></title>
<?php output_og_tags($post ?? null); ?>
<?php output_schema($post ?? null); ?>
<?php output_robots_meta($post ?? null); ?>
<link rel="stylesheet" href="<?= theme_url('assets/css/style.css') ?>">
<?php output_theme_customization_css(); ?>
<?php output_head_scripts(); ?>
<?php output_favicon_tags(); ?>
</head>
output_theme_customization_css() adds the colours and fonts chosen in the theme customiser. output_head_scripts() prints the analytics and verification codes entered in the panel.
Template Functions
These functions replace the hard-coded content of the HTML:
get_setting('general', 'site_name'): reads site settingsget_posts(['limit' => 6, 'category' => 'news']): fetches posts with filtersget_categories(): lists categoriesget_featured_image($post, 'medium'): returns the featured image URLget_featured_picture(): prints a picture tag with AVIF and WebP sources and a srcsettheme_url('assets/...'): returns the address of a file in the theme folderformat_date($date): formats a date for the site languagee($text): prints text safely into HTML
get_posts() returns plain PHP arrays rather than objects; in a template you read $post['title'].
The Post Card
The post card is used on the homepage, in archives, in search and in the sidebar. Writing it as a partial means there is one file to edit when it needs to change:
<?php // partials/post-card.php
$img = get_featured_image($post, 'medium'); ?>
<article class="post-card">
<?php if ($img): ?>
<a href="<?= SITE_URL ?>/<?= e($post['slug']) ?>" class="card-image">
<img src="<?= e($img) ?>" alt="<?= e($post['title']) ?>"
width="800" height="500" loading="lazy">
</a>
<?php endif; ?>
<div class="card-content">
<h2><a href="<?= SITE_URL ?>/<?= e($post['slug']) ?>"><?= e($post['title']) ?></a></h2>
<p><?= e($post['excerpt'] ?? '') ?></p>
<time datetime="<?= e($post['published_at']) ?>"><?= format_date($post['published_at']) ?></time>
</div>
</article>
A post's address is the site address plus the post slug (/post-slug), with no prefix such as /blog/. Writing width and height on the image keeps content from jumping while the page loads.
Dark Mode
Define every colour as a CSS custom property. Make light mode the default and define dark mode under a [data-theme="dark"] selector. The script that saves the choice to localStorage should apply it before the page is drawn; otherwise the wrong colours flash for a moment on load.
Before Going Live
- Run Lighthouse on the homepage, a post and an archive.
- Try navigating the page with the keyboard only.
- Check the layout at 375, 768, 1024 and 1440 pixels wide.
- Make sure dark mode works on every page.
- Keep
DEBUG_MODEon while developing so undefined-variable warnings show up early.