Theme Examples
Complete working theme examples to learn from.
Minimal Blog Theme
A simple, clean blog theme perfect for personal blogs.
File Structure
minimal-blog/
├── theme.json
├── index.js
├── views/
│ ├── layouts/
│ │ └── main.html
│ ├── pages/
│ │ ├── home.html
│ │ ├── post-detail.html
│ │ ├── posts.html
│ │ ├── category-detail.html
│ │ └── error.html
│ └── partials/
│ ├── head.html
│ ├── header.html
│ ├── footer.html
│ └── post-card.html
└── assets/
├── css/
│ └── main.css
└── js/
└── main.jstheme.json
{
"name": "Minimal Blog",
"version": "1.0.0",
"description": "A clean, minimal blog theme",
"author": "Your Name",
"assets": {
"css": ["css/main.css"],
"js": ["js/main.js"]
},
"supports": {
"customLogo": true,
"menus": ["primary"],
"widgets": ["sidebar"],
"featuredImages": true
},
"settings": [
{
"key": "accentColor",
"type": "color",
"label": "Accent Color",
"default": "#2563eb"
},
{
"key": "showSidebar",
"type": "boolean",
"label": "Show Sidebar",
"default": true
}
]
}index.js
module.exports = function(sdk) {
return {
helpers: {
formatDate: (date) => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
},
readTime: (content) => {
const text = content.replace(/<[^>]*>/g, '');
const words = text.split(/\s+/).length;
return Math.ceil(words / 200);
}
}
};
};views/layouts/main.html
<!DOCTYPE html>
<html lang="{{ settings.language || 'en' }}">
<head>
{{{ include('partials/head') }}}
</head>
<body>
{{{ include('partials/header') }}}
<main class="main">
<div class="container">
{% if (themeSettings.showSidebar) { %}
<div class="layout-sidebar">
<div class="content">
{{{ content }}}
</div>
<aside class="sidebar">
{{{ sdk.ui.widgetArea('sidebar') }}}
</aside>
</div>
{% } else { %}
{{{ content }}}
{% } %}
</div>
</main>
{{{ include('partials/footer') }}}
<script src="{{ themePath }}/assets/js/main.js"></script>
</body>
</html>views/partials/head.html
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ pageTitle || settings.siteName }}</title>
<meta name="description" content="{{ metaDescription || settings.siteDescription }}">
{% if (canonicalUrl) { %}
<link rel="canonical" href="{{ canonicalUrl }}">
{% } %}
<link rel="stylesheet" href="{{ themePath }}/assets/css/main.css">
<style>
:root {
--color-accent: {{ themeSettings.accentColor || '#2563eb' }};
}
</style>views/partials/header.html
<header class="header">
<div class="container">
<a href="/" class="logo">
{% if (settings.logo) { %}
<img src="{{ settings.logo }}" alt="{{ settings.siteName }}">
{% } else { %}
{{ settings.siteName }}
{% } %}
</a>
<nav class="nav">
{{{ sdk.ui.menu('primary') }}}
</nav>
<button class="mobile-menu-toggle" aria-label="Toggle menu">
<span></span>
</button>
</div>
</header>views/partials/footer.html
<footer class="footer">
<div class="container">
<p>© {{ new Date().getFullYear() }} {{ settings.siteName }}. All rights reserved.</p>
</div>
</footer>views/partials/post-card.html
<article class="post-card">
{% if (post.featuredImage) { %}
<a href="{{ post.url }}" class="post-card__image">
<img src="{{ post.featuredImage }}" alt="{{ post.title }}" loading="lazy">
</a>
{% } %}
<div class="post-card__content">
{% if (post.category) { %}
<a href="{{ post.category.url }}" class="post-card__category">
{{ post.category.name }}
</a>
{% } %}
<h2 class="post-card__title">
<a href="{{ post.url }}">{{ post.title }}</a>
</h2>
{% if (post.excerpt) { %}
<p class="post-card__excerpt">{{ post.excerpt }}</p>
{% } %}
<div class="post-card__meta">
<time datetime="{{ post.createdAt }}">
{{ helpers.formatDate(post.createdAt) }}
</time>
<span class="read-time">{{ helpers.readTime(post.content) }} min read</span>
</div>
</div>
</article>views/pages/home.html
<div class="home">
{% if (featuredPost) { %}
<section class="featured">
<article class="featured-post">
{% if (featuredPost.featuredImage) { %}
<img src="{{ featuredPost.featuredImage }}" alt="{{ featuredPost.title }}">
{% } %}
<div class="featured-post__content">
<h1><a href="{{ featuredPost.url }}">{{ featuredPost.title }}</a></h1>
<p>{{ featuredPost.excerpt }}</p>
<a href="{{ featuredPost.url }}" class="btn">Read More</a>
</div>
</article>
</section>
{% } %}
<section class="latest-posts">
<h2>Latest Posts</h2>
{% if (latestPosts && latestPosts.length > 0) { %}
<div class="posts-grid">
{% latestPosts.forEach(post => { %}
{{{ include('partials/post-card', { post }) }}}
{% }) %}
</div>
{% } else { %}
<p>No posts yet.</p>
{% } %}
</section>
</div>views/pages/post-detail.html
<article class="post">
<header class="post__header">
{% if (post.category) { %}
<a href="{{ post.category.url }}" class="post__category">
{{ post.category.name }}
</a>
{% } %}
<h1 class="post__title">{{ post.title }}</h1>
<div class="post__meta">
{% if (post.author) { %}
<span class="post__author">By {{ post.author.name }}</span>
{% } %}
<time datetime="{{ post.createdAt }}">
{{ helpers.formatDate(post.createdAt) }}
</time>
<span class="post__read-time">{{ helpers.readTime(post.content) }} min read</span>
</div>
</header>
{% if (post.featuredImage) { %}
<figure class="post__image">
<img src="{{ post.featuredImage }}" alt="{{ post.title }}">
</figure>
{% } %}
<div class="post__content">
{{{ post.content }}}
</div>
<footer class="post__footer">
{{{ sdk.ui.shareButtons(post.url, post.title) }}}
{% if (post.tags && post.tags.length > 0) { %}
<div class="post__tags">
{% post.tags.forEach(tag => { %}
<a href="{{ tag.url }}" class="tag">{{ tag.name }}</a>
{% }) %}
</div>
{% } %}
</footer>
</article>
{% if (post.author) { %}
<div class="author-box">
{% if (post.author.avatar) { %}
<img src="{{ post.author.avatar }}" alt="{{ post.author.name }}" class="author-box__avatar">
{% } %}
<div class="author-box__content">
<h3>{{ post.author.name }}</h3>
{% if (post.author.bio) { %}
<p>{{ post.author.bio }}</p>
{% } %}
</div>
</div>
{% } %}
{{{ sdk.ui.comments(post.id) }}}views/pages/posts.html
<div class="archive">
<h1>All Posts</h1>
{% if (posts && posts.length > 0) { %}
<div class="posts-grid">
{% posts.forEach(post => { %}
{{{ include('partials/post-card', { post }) }}}
{% }) %}
</div>
{% if (pagination && pagination.total > 1) { %}
{{{ sdk.ui.pagination(pagination) }}}
{% } %}
{% } else { %}
<p>No posts found.</p>
{% } %}
</div>views/pages/category-detail.html
<div class="archive archive--category">
<h1>{{ category.name }}</h1>
{% if (category.description) { %}
<p class="archive__description">{{ category.description }}</p>
{% } %}
{% if (posts && posts.length > 0) { %}
<div class="posts-grid">
{% posts.forEach(post => { %}
{{{ include('partials/post-card', { post }) }}}
{% }) %}
</div>
{% if (pagination && pagination.total > 1) { %}
{{{ sdk.ui.pagination(pagination) }}}
{% } %}
{% } else { %}
<p>No posts in this category yet.</p>
{% } %}
</div>views/pages/error.html
<div class="error-page">
<h1>{{ error.code || '404' }}</h1>
<p>{{ error.message || 'Page not found' }}</p>
<a href="/" class="btn">Back to Home</a>
</div>assets/css/main.css
/* Reset */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
/* Variables */
:root {
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-bg: #ffffff;
--color-bg-alt: #f9fafb;
--color-border: #e5e7eb;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-serif: Georgia, 'Times New Roman', serif;
}
/* Base */
body {
font-family: var(--font-sans);
font-size: 16px;
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header */
.header {
padding: 20px 0;
border-bottom: 1px solid var(--color-border);
}
.header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
text-decoration: none;
color: var(--color-text);
}
.logo img {
height: 40px;
}
.nav ul {
display: flex;
gap: 30px;
list-style: none;
margin: 0;
padding: 0;
}
.nav a {
text-decoration: none;
color: var(--color-text);
transition: color 0.2s;
}
.nav a:hover {
color: var(--color-accent);
}
/* Layout */
.main {
padding: 40px 0;
min-height: 60vh;
}
.layout-sidebar {
display: grid;
grid-template-columns: 1fr 300px;
gap: 40px;
}
@media (max-width: 768px) {
.layout-sidebar {
grid-template-columns: 1fr;
}
}
/* Posts Grid */
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 30px;
}
/* Post Card */
.post-card {
border: 1px solid var(--color-border);
border-radius: 8px;
overflow: hidden;
transition: box-shadow 0.3s;
}
.post-card:hover {
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.post-card__image img {
width: 100%;
height: 200px;
object-fit: cover;
}
.post-card__content {
padding: 20px;
}
.post-card__category {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--color-accent);
text-decoration: none;
}
.post-card__title {
font-size: 1.25rem;
margin: 10px 0;
}
.post-card__title a {
text-decoration: none;
color: var(--color-text);
}
.post-card__excerpt {
color: var(--color-text-muted);
margin: 0 0 15px;
}
.post-card__meta {
font-size: 0.875rem;
color: var(--color-text-muted);
display: flex;
gap: 15px;
}
/* Single Post */
.post__header {
margin-bottom: 30px;
}
.post__category {
display: inline-block;
font-size: 0.875rem;
font-weight: 600;
color: var(--color-accent);
text-decoration: none;
margin-bottom: 10px;
}
.post__title {
font-size: 2.5rem;
line-height: 1.2;
margin: 0 0 15px;
}
.post__meta {
color: var(--color-text-muted);
display: flex;
gap: 20px;
}
.post__image {
margin: 0 0 30px;
}
.post__image img {
width: 100%;
border-radius: 8px;
}
.post__content {
font-family: var(--font-serif);
font-size: 1.125rem;
line-height: 1.8;
}
.post__content h2 { font-size: 1.75rem; margin: 40px 0 20px; }
.post__content h3 { font-size: 1.5rem; margin: 30px 0 15px; }
.post__content p { margin: 0 0 20px; }
.post__content img { max-width: 100%; height: auto; border-radius: 8px; }
/* Footer */
.footer {
padding: 40px 0;
border-top: 1px solid var(--color-border);
text-align: center;
color: var(--color-text-muted);
}
/* Button */
.btn {
display: inline-block;
padding: 10px 20px;
background: var(--color-accent);
color: white;
text-decoration: none;
border-radius: 5px;
font-weight: 500;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
/* Error Page */
.error-page {
text-align: center;
padding: 80px 20px;
}
.error-page h1 {
font-size: 6rem;
margin: 0;
color: var(--color-accent);
}This minimal theme provides a solid foundation that you can customize and extend. Copy it, modify the styles, add more features, and make it your own!