DEV Community

Cover image for Ghost CMS Theme Development: A Full Guide
Utkarsh Vishwas
Utkarsh Vishwas

Posted on

Ghost CMS Theme Development: A Full Guide

Hello there! πŸ‘‹ Let's dive deep into the exciting world of Ghost CMS theme development. This guide will be your companion to creating beautiful, custom themes for your Ghost blog, even if you're just starting out. We'll break down everything from the basics to building a simple, functional theme example.

Ghost CMS is known for its clean interface, focus on writing, and flexibility. One of the most powerful ways to customize Ghost is through themes. Themes control the entire front-end look and feel of your blog – from layout and typography to colors and functionality.

Why Learn Ghost Theme Development?

  • Total Control: Escape generic templates and build a website perfectly tailored to your brand and content.
  • Unique Design: Stand out from the crowd with a distinctive visual identity.
  • Enhanced Functionality: Extend Ghost beyond its core features with custom JavaScript and integrations.
  • Skill Enhancement: Learn valuable web development skills (HTML, CSS, JavaScript, Handlebars).
  • Community Contribution: Create and share your themes with the growing Ghost community.

What You'll Learn in This Guide:

  1. Prerequisites & Setup: Tools you'll need and setting up your local Ghost environment.
  2. Understanding Theme Structure: Exploring the essential files and folders within a Ghost theme.
  3. Essential Theme Files Explained: Deep dive into index.hbs, post.hbs, page.hbs, default.hbs, and more.
  4. Handlebars Templating: The Heart of Ghost Themes: Mastering the syntax for dynamic content.
  5. Ghost Helpers: Your Theme's Superpowers: Utilizing built-in helpers for accessing data and functionality.
  6. Styling Your Theme with CSS: Making it visually appealing.
  7. Adding Interactivity with JavaScript: Enhancing user experience.
  8. Theme Settings: Making Your Theme Configurable: Allowing users to customize through the Ghost admin.
  9. Testing and Debugging Your Theme: Ensuring everything works smoothly.
  10. Packaging and Uploading Your Theme: Getting your theme ready for use on your Ghost blog.
  11. Easy Example: Building a Simple Blog Theme (Step-by-Step): A practical, hands-on project to solidify your learning.
  12. Further Learning and Resources: Where to go next to deepen your knowledge.

Let's begin!


1. Prerequisites & Setup

Before we start coding, ensure you have these tools ready:

  • Text Editor: A good code editor is crucial. Recommended options:
    • VS Code (Free & Powerful): My top recommendation with excellent Handlebars and web development support.
    • Sublime Text (Free Trial, Paid): Another popular choice known for its speed and flexibility.
    • Atom (Free & Open Source - now archived but still usable): Developed by GitHub, customizable.
  • Node.js and npm (Node Package Manager): Essential for running Ghost locally and managing dependencies.
  • Ghost CLI (Command Line Interface): Makes managing your local Ghost instance a breeze.

    • Install Ghost CLI globally: Open your terminal/command prompt and run:

      npm install ghost-cli@latest -g
      
  • Local Ghost Instance: You need a running Ghost blog to test your theme. We'll set this up now.

Setting up a Local Ghost Instance:

  1. Choose a Directory: Create a folder on your computer where you want to install Ghost (e.g., ghost-dev).
  2. Navigate to the Directory: Open your terminal/command prompt and cd into the directory you just created.

    cd ghost-dev
    
  3. Install Ghost Locally: Run the Ghost CLI install command:

    ghost install local
    

    Follow the prompts. Ghost CLI will download and set up a local instance of Ghost using SQLite3 for your database.

  4. Start Ghost: After installation, navigate into the newly created Ghost directory (usually the same as your chosen directory).

    cd ghost-dev  # If you are not already inside
    ghost start
    
  5. Access Ghost:

    • Frontend: Open your browser and go to http://localhost:2368. You should see your Ghost blog's homepage (likely the default Casper theme).
    • Admin Panel: Go to http://localhost:2368/ghost. Create your admin account.

Great! You now have a local Ghost environment ready for theme development. Let's move on to understanding the theme structure.


2. Understanding Theme Structure

Ghost themes have a specific folder structure that Ghost recognizes. Let's explore the key components:

your-theme-name/
β”œβ”€β”€ assets/           # Static assets (CSS, JavaScript, images, fonts)
β”‚   β”œβ”€β”€ css/
β”‚   β”œβ”€β”€ js/
β”‚   └── images/
β”œβ”€β”€ default.hbs      # The base layout template
β”œβ”€β”€ index.hbs        # Homepage template (list of posts)
β”œβ”€β”€ post.hbs         # Single post template
β”œβ”€β”€ page.hbs         # Single page template
β”œβ”€β”€ tag.hbs          # Tag archive page
β”œβ”€β”€ author.hbs       # Author archive page
β”œβ”€β”€ partials/        # Reusable template snippets
β”‚   β”œβ”€β”€ head.hbs      # Example: Reusable <head> content
β”‚   └── ...
β”œβ”€β”€ package.json     # Theme metadata and settings (important!)
β”œβ”€β”€ theme.json       # (Optional, for Ghost 5.0+ for theme cards)
└── README.md        # (Optional, theme documentation)
Enter fullscreen mode Exit fullscreen mode

Key Folders and Files Explained:

  • assets/:
    • css/: Your theme's stylesheets (.css files). style.css is conventionally the main CSS file.
    • js/: JavaScript files (.js) for theme interactivity.
    • images/: Images used in your theme (logos, icons, etc.).
  • .hbs Files (Handlebars Templates): These are the core template files that define the structure and content of different pages on your Ghost blog.
    • default.hbs: The layout template. It's the base structure for all other .hbs files in your theme. Think of it as the "skeleton" of your pages. It typically includes the <head>, <body>, header, footer, and placeholders ({{{body}}}) where the content from other templates is injected.
    • index.hbs: Used for the homepage of your blog, usually displaying a list of your latest posts.
    • post.hbs: Template for displaying a single blog post.
    • page.hbs: Template for displaying static pages (e.g., "About Us", "Contact").
    • tag.hbs: Displays a tag archive page, listing posts associated with a specific tag.
    • author.hbs: Displays an author archive page, listing posts by a specific author.
    • Other potential .hbs files: error.hbs (for error pages), subscribe.hbs (for the subscription page), amp.hbs (for AMP versions of pages), etc. You can also create custom template files.
  • partials/: This folder contains reusable template snippets (Handlebars partials). Partials are useful for code that you want to use in multiple templates (e.g., header, footer, post cards, navigation menus). This promotes code reusability and organization.
  • package.json: A crucial file! It contains:
    • Theme Metadata: Name, version, description, author, license, etc.
    • Theme Settings: Defines customizable settings that users can adjust in the Ghost admin panel (e.g., logo upload, color pickers). This is how you make your theme configurable.
  • theme.json (Optional, Ghost 5.0+): Used for displaying a theme card in the Ghost admin theme marketplace. Primarily for themes intended for public distribution.
  • README.md (Optional): Good practice to include a README file with documentation about your theme (instructions, credits, etc.).

Theme Location:

Ghost looks for themes in the content/themes/ directory within your Ghost installation. When you create a new theme, you'll place its folder inside content/themes/.

Let's create our first theme folder!

  1. Navigate to content/themes/: In your terminal, navigate to the content/themes/ directory within your Ghost installation. For a local install, it's likely inside your Ghost project folder.

    cd content/themes
    
  2. Create a Theme Folder: Create a new folder for your theme. Let's call it my-first-theme.

    mkdir my-first-theme
    cd my-first-theme
    
  3. Create Essential Files: Inside my-first-theme, create the following basic files:

    touch index.hbs default.hbs package.json assets
    mkdir assets css js images
    touch assets/css/style.css
    

    You should now have the basic structure for your theme.

Next, we'll fill in the essential theme files with content.


3. Essential Theme Files Explained

Let's populate our essential theme files with some basic content to get started.

1. default.hbs (Layout Template):

This is the base structure for all your pages.

<!DOCTYPE html>
<html lang="{{@site.locale}}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{meta_title}}</title>
    <meta name="description" content="{{meta_description}}">

    <link rel="stylesheet" href="{{asset "css/style.css"}}">

    {{ghost_head}}
</head>
<body class="{{body_class}}">

    <header class="site-header">
        <h1><a href="{{@site.url}}">{{@site.title}}</a></h1>
        <p>{{@site.description}}</p>
    </header>

    <main class="site-main">
        {{{body}}}  </main>

    <footer class="site-footer">
        &copy; {{date format="YYYY"}} <a href="{{@site.url}}">{{@site.title}}</a> - Powered by <a href="https://ghost.org/" target="_blank" rel="noopener">Ghost</a>
    </footer>

    <script src="{{asset "js/index.js"}}"></script>
    {{ghost_foot}}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • <!DOCTYPE html>, <html>, <head>, <body>: Standard HTML structure.
  • {{@site.locale}}: Ghost helper to output the site's locale setting.
  • <title>{{meta_title}}</title>: {{meta_title}} helper outputs the page title (set in Ghost admin or post/page settings).
  • <meta name="description" content="{{meta_description}}">: {{meta_description}} outputs the meta description.
  • <link rel="stylesheet" href="{{asset "css/style.css"}}">: Loads our style.css file using the {{asset}} helper (more on helpers later).
  • {{ghost_head}}: Essential Ghost helper! Injects necessary <head> elements required by Ghost (like canonical URLs, RSS links, structured data, etc.). Always include this in your <head>!
  • <body class="{{body_class}}">: {{body_class}} helper adds CSS classes to the <body> based on the context (e.g., home-template, post-template, page-template). Useful for page-specific styling.
  • <header class="site-header">...</header>: Basic site header with site title and description.
  • <main class="site-main">...</main>: The main content area.
  • {{{body}}}: Crucial! This is a triple-brace {{{body}}} helper. It acts as a placeholder where Ghost will inject the content from other templates (like index.hbs, post.hbs, page.hbs). Use triple braces {{{ }}} here to render HTML content safely.
  • <footer class="site-footer">...</footer>: Basic site footer.
  • <script src="{{asset "js/index.js"}}"></script>: Loads our index.js file.
  • {{ghost_foot}}: Essential Ghost helper! Injects necessary JavaScript code and elements required by Ghost right before the closing </body> tag. Always include this in your <body>!

2. index.hbs (Homepage Template):

This template will display a list of your latest blog posts on the homepage.

{{!< default}}  <div class="post-feed">
    {{#foreach posts}}  <article class="{{post_class}}"> <header class="post-header">
                <h2 class="post-title"><a href="{{url}}">{{title}}</a></h2>
                <time class="post-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM DD, YYYY"}}</time>
            </header>
            <section class="post-excerpt">
                <p>{{excerpt}}</p>
            </section>
            <footer class="post-footer">
                <a href="{{url}}" class="read-more">Read More β†’</a>
            </footer>
        </article>
    {{/foreach}}
</div>

{{pagination}}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • {{!< default}}: Extends the default.hbs layout. This tells Ghost to use default.hbs as the base layout for this template. The content within index.hbs will be injected into the {{{body}}} placeholder in default.hbs.
  • <div class="post-feed">...</div>: Container for the post list.
  • {{#foreach posts}}...{{/foreach}}: Handlebars foreach loop. Iterates over the posts array, which Ghost makes available on the homepage context. For each post in the array, the code inside the loop is repeated.
  • <article class="{{post_class}}">: Each post is wrapped in an <article> element. {{post_class}} helper adds CSS classes to the <article> based on the post's context (e.g., post, featured).
  • <h2 class="post-title"><a href="{{url}}">{{title}}</a></h2>: Displays the post title as a link to the post's URL. {{title}} outputs the post title, and {{url}} outputs the post URL.
  • <time class="post-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM DD, YYYY"}}</time>: Displays the post date using the {{date}} helper with different formatting options.
  • <section class="post-excerpt"><p>{{excerpt}}</p></section>: Displays the post excerpt using the {{excerpt}} helper.
  • <a href="{{url}}" class="read-more">Read More β†’</a>: "Read More" link to the full post.
  • {{pagination}}: Ghost helper for pagination. Displays pagination links (e.g., "Next Page", "Previous Page") if there are more posts than can fit on one page.

3. post.hbs (Single Post Template):

This template will display a single blog post.

{{!< default}}  <article class="post-full">
    <header class="post-full-header">
        <h1 class="post-full-title">{{title}}</h1>
        <div class="post-full-meta">
            By <a href="{{author.url}}">{{author.name}}</a> on <time datetime="{{published_at format="YYYY-MM-DD"}}">{{published_at format="MMM DD, YYYY"}}</time>
        </div>
    </header>

    <section class="post-full-content">
        {{content}}  </section>

    <footer class="post-full-footer">
        {{#if tags}}
        <div class="post-tags">
            Tagged with:
            {{#foreach tags}}
                <a href="{{url}}">{{name}}</a>{{#unless @last}}, {{/unless}}
            {{/foreach}}
        </div>
        {{/if}}
    </footer>
</article>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • {{!< default}}: Extends default.hbs layout.
  • <article class="post-full">...</article>: Container for the single post.
  • <h1 class="post-full-title">{{title}}</h1>: Displays the post title.
  • <div class="post-full-meta">...</div>: Post metadata (author and date).
  • By <a href="{{author.url}}">{{author.name}}</a>: Displays the author's name as a link to their author archive page. {{author.name}} outputs the author's name, and {{author.url}} outputs the author's URL.
  • <time datetime="{{published_at format="YYYY-MM-DD"}}">{{published_at format="MMM DD, YYYY"}}</time>: Displays the post's published date using {{published_at}} helper.
  • <section class="post-full-content">{{content}}</section>: {{content}} helper! This is where the main content of the post (body text, images, etc.) is rendered. Crucial for displaying post content!
  • <footer class="post-full-footer">...</footer>: Post footer, including tags (if any).
  • {{#if tags}}...{{/if}}: Handlebars if conditional. Checks if the post has any tags.
  • {{#foreach tags}}...{{/foreach}}: If there are tags, loop through them to display tag links.
  • <a href="{{url}}">{{name}}</a>: Displays each tag as a link to the tag archive page. {{name}} outputs the tag name, and {{url}} outputs the tag URL.
  • {{#unless @last}}, {{/unless}}: Handlebars unless helper and @last variable. Adds a comma and space after each tag unless it's the last tag in the list (to avoid a trailing comma).

4. page.hbs (Single Page Template):

This template is similar to post.hbs but for static pages.

{{!< default}}  <article class="page-full">
    <header class="page-full-header">
        <h1 class="page-full-title">{{title}}</h1>
    </header>

    <section class="page-full-content">
        {{content}}  </section>
</article>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Very similar to post.hbs, but simpler. It primarily displays the page title and content using {{title}} and {{content}} helpers.

5. assets/css/style.css (Basic CSS):

Let's add some minimal CSS to make our theme a bit more visually presentable.

/* Minimal CSS for our first theme */

body {
    font-family: sans-serif;
    line-height: 1.6;
    margin: 20px;
}

.site-header {
    text-align: center;
    margin-bottom: 30px;
}

.site-header h1 a {
    text-decoration: none;
    color: #333;
}

.site-main {
    max-width: 800px;
    margin: 0 auto;
}

.post-feed {
    margin-bottom: 40px;
}

.post-title a {
    text-decoration: none;
    color: #333;
}

.post-date {
    font-size: 0.9em;
    color: #777;
    display: block;
    margin-top: 5px;
}

.post-excerpt p {
    margin-bottom: 15px;
}

.read-more {
    display: inline-block;
    padding: 8px 15px;
    background-color: #eee;
    text-decoration: none;
    color: #333;
    border-radius: 5px;
}

.post-full-title {
    margin-bottom: 20px;
}

.post-full-meta {
    font-size: 0.9em;
    color: #777;
    margin-bottom: 30px;
}

.post-full-content {
    margin-bottom: 40px;
}

.site-footer {
    text-align: center;
    padding-top: 20px;
    border-top: 1px solid #eee;
    color: #777;
}
Enter fullscreen mode Exit fullscreen mode

6. package.json (Theme Metadata):

This is a JSON file that describes your theme. Crucial for Ghost to recognize your theme!

{
    "name": "my-first-theme",
    "version": "0.1.0",
    "description": "A very simple Ghost theme for learning purposes.",
    "author": "Your Name",
    "engines": {
        "ghost": ">=5.0.0",
        "ghost-api": "v5"
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • name: The name of your theme folder (must match the folder name).
  • version: Theme version number (start with 0.1.0).
  • description: A brief description of your theme.
  • author: Your name or organization.
  • engines: Specifies the minimum Ghost version and API version your theme is compatible with. Make sure this is compatible with your local Ghost install.

Activate Your Theme:

  1. Restart Ghost: In your terminal, stop and restart your Ghost instance:

    ghost restart
    
  2. Log in to Ghost Admin: Go to http://localhost:2368/ghost.

  3. Navigate to "Settings" -> "Design" -> "Themes".

  4. Find "My First Theme" in the list of available themes.

  5. Click "Activate" next to "My First Theme".

View Your Theme! Go back to your blog's homepage (http://localhost:2368). You should now see your very basic, but functional, Ghost theme in action!

Congratulations! You've created your first basic Ghost theme. Now let's delve deeper into Handlebars.


4. Handlebars Templating: The Heart of Ghost Themes

Handlebars is a simple and powerful templating engine used by Ghost. It allows you to dynamically insert data into your HTML templates.

Basic Handlebars Syntax:

  • Variables (Outputting Data): Use double curly braces {{ }} to output variable values.

    <h1>{{title}}</h1>
    
  • Comments: Use {{! -- your comment -- }} for comments that won't be rendered in the output.

    {{! -- This is a comment in Handlebars -- }}
    
  • Helpers (Functions): Helpers are functions that perform specific tasks. They are also called using double curly braces {{ }} but usually involve more complex syntax or parameters.

    <time>{{date format="MMM DD, YYYY"}}</time>
    
  • Blocks (Logic & Iteration): Block helpers are used for logic (conditionals) and iteration (loops). They start with {{#helper}} and end with {{/helper}}.

    {{#if author}}  <p>By {{author.name}}</p>
    {{/if}}
    
    {{#foreach posts}}  <h2>{{title}}</h2>
    {{/foreach}}
    

Key Handlebars Concepts for Ghost Themes:

  • Context: Ghost provides context data to your templates. This is the data that Handlebars can access and display. The context depends on the type of page being rendered.

    • Homepage (index.hbs): Context includes posts (array of latest posts), pagination, @site (site settings), etc.
    • Single Post (post.hbs): Context includes post (the single post object), author, tags, @site, etc.
    • Single Page (page.hbs): Context includes page (the single page object), @site, etc.
    • Tag Archive (tag.hbs): Context includes tag (the tag object), posts (posts with that tag), pagination, @site, etc.
    • Author Archive (author.hbs): Context includes author (the author object), posts (posts by that author), pagination, @site, etc.
    • @site: A global context object available in all templates. It contains site-wide settings like title, description, url, logo, cover_image, locale, etc. You access these using @site.property_name (e.g., @site.title, @site.url).
  • Data Properties: Objects like post, page, author, tag have properties that you can access in your templates. For example:

    • post.title: The title of a post.
    • post.excerpt: The excerpt of a post.
    • post.url: The URL of a post.
    • post.content: The full content of a post.
    • post.published_at: The published date of a post.
    • author.name: The author's name.
    • author.url: The author's URL.
    • tag.name: The tag name.
    • tag.url: The tag URL.

Example: Using Handlebars Helpers and Context in index.hbs

Let's revisit our index.hbs and highlight the Handlebars usage:

{{!< default}}

<div class="post-feed">
    {{#foreach posts}}  <article class="{{post_class}}"> <header class="post-header">
                <h2 class="post-title"><a href="{{url}}">{{title}}</a></h2> <time class="post-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM DD, YYYY"}}</time> </header>
            <section class="post-excerpt">
                <p>{{excerpt}}</p> </section>
            <footer class="post-footer">
                <a href="{{url}}" class="read-more">Read More β†’</a> </footer>
        </article>
    {{/foreach}}
</div>

{{pagination}} 
Enter fullscreen mode Exit fullscreen mode

Key Takeaways about Handlebars in Ghost:

  • Dynamic Content: Handlebars allows you to display dynamic content from your Ghost blog (posts, pages, settings, etc.) in your themes.
  • Context is King: Understanding the context data available in each template is crucial for accessing the correct information.
  • Helpers Simplify Tasks: Ghost helpers provide pre-built functionality for common tasks like formatting dates, generating URLs, outputting site settings, and more.
  • Logic and Iteration: Block helpers (if, foreach, etc.) allow you to add logic and loops to your templates for more complex layouts and data handling.

Next, we'll explore Ghost helpers in more detail.


5. Ghost Helpers: Your Theme's Superpowers

Ghost helpers are pre-built functions that simplify common tasks in your themes. They provide access to data, generate URLs, format dates, and more. They are essential for building dynamic and functional Ghost themes.

Ghost helpers are used within your Handlebars templates using double curly braces {{ }}.

Categories of Ghost Helpers:

Ghost helpers can be broadly categorized into:

  • Core Helpers: Essential helpers for basic theme functionality.
  • Content Helpers: Helpers for accessing and formatting post/page content.
  • Image Helpers: Helpers for working with post/page feature images and site logos.
  • URL Helpers: Helpers for generating various URLs within your Ghost blog.
  • Date Helpers: Helpers for formatting dates in different ways.
  • Conditional Helpers: Helpers for conditional logic within your templates.
  • Member Helpers (for Ghost Membership features): Helpers related to Ghost Memberships (subscriptions, member data, etc.).
  • Social Helpers: Helpers for social sharing and linking.
  • Etc.: There are other specialized helpers for specific features.

Some Important Ghost Helpers You'll Use Frequently:

  • {{asset "path/to/asset.file"}} (Core Helper):

    • Generates the correct URL for assets (CSS, JavaScript, images) in your assets/ folder.
    • Example: <link rel="stylesheet" href="{{asset "css/style.css"}}">
  • {{body}} (Core Helper):

    • Placeholder in default.hbs where content from other templates is injected. Use triple braces {{{body}}}.
    • Example: <main class="site-main">{{{body}}}</main>
  • {{body_class}} (Core Helper):

    • Adds context-aware CSS classes to the <body> tag.
    • Example: <body class="{{body_class}}">
  • {{ghost_head}} (Core Helper):

    • Injects essential <head> elements required by Ghost. Always include this in <head>!
    • Example: {{ghost_head}}
  • {{ghost_foot}} (Core Helper):

    • Injects essential JavaScript code and elements before </body>. Always include this before </body>!
    • Example: {{ghost_foot}}
  • {{title}} (Content Helper):

    • Outputs the title of a post, page, tag, author, or site (depending on the context).
    • Example: <h1>{{title}}</h1>
  • {{content}} (Content Helper):

    • Outputs the main content of a post or page. Use triple braces {{{content}}} to render HTML safely.
    • Example: <section class="post-full-content">{{{content}}}</section>
  • {{excerpt}} (Content Helper):

    • Outputs the excerpt of a post (automatically generated or manually set).
    • Example: <p>{{excerpt}}</p>
  • {{url}} (URL Helper):

    • Generates the URL for a post, page, tag, author, or the site itself (depending on the context).
    • Example: <a href="{{url}}">Read More</a>
  • {{author.name}}, {{author.url}}, {{author.profile_image}}, etc. (Content Helpers - Author Properties):

    • Access properties of the author object.
    • Example: <a href="{{author.url}}">{{author.name}}</a>
  • {{tags}}, {{tags separator=","}}, {{tags before="Tags: "}} (Content Helpers - Tag Properties):

    • Output tags associated with a post. You can customize the separator, prefix, etc.
    • Example: {{tags separator=", "}}
  • {{date format="MMM DD, YYYY"}} (Date Helper):

    • Formats dates. You can use various date formatting options (Moment.js format strings).
    • Example: <time>{{date format="MMM DD, YYYY"}}</time>
  • {{pagination}} (Core Helper):

    • Displays pagination links (if needed).
    • Example: {{pagination}}
  • {{#foreach posts}}...{{/foreach}} (Block Helper - Iteration):

    • Loops through an array of posts.
    • Example: {{#foreach posts}}...{{/foreach}}
  • {{#if condition}}...{{/if}}, {{#unless condition}}...{{/unless}} (Block Helpers - Conditional Logic):

    • Conditional rendering based on a condition.
    • Example: {{#if tags}}...{{/if}}

Exploring More Helpers:

The official Ghost Theme documentation is your best resource for discovering all available helpers and their usage:

Experiment with Helpers:

Try modifying your index.hbs or post.hbs templates to use different helpers. For example:

  • In index.hbs, add author names below each post title:
   <header class="post-header">
       <h2 class="post-title"><a href="{{url}}">{{title}}</a></h2>
       <p class="post-author">By {{author.name}}</p>  <time class="post-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM DD, YYYY"}}</time>
   </header>
Enter fullscreen mode Exit fullscreen mode
  • In post.hbs, add the author's profile image (if available):

    <div class="post-full-meta">
        By
        {{#if author.profile_image}}  <img class="author-profile-image" src="{{author.profile_image}}" alt="{{author.name}}" />
        {{/if}}
        <a href="{{author.url}}">{{author.name}}</a>
        on <time datetime="{{published_at format="YYYY-MM-DD"}}">{{published_at format="MMM DD, YYYY"}}</time>
    </div>
    

Remember to restart Ghost after making changes to your theme files to see the updates.

Understanding and utilizing Ghost helpers is crucial for building powerful and feature-rich Ghost themes. Continue to explore the documentation and experiment with different helpers!


6. Styling Your Theme with CSS

CSS (Cascading Style Sheets) is essential for making your Ghost theme visually appealing and on-brand. You'll use CSS to control:

  • Layout: Positioning elements on the page (grids, flexbox, etc.).
  • Typography: Fonts, font sizes, line height, letter spacing, etc.
  • Colors: Backgrounds, text colors, link colors, etc.
  • Spacing: Margins, padding.
  • Responsiveness: Making your theme look good on different screen sizes (desktop, tablet, mobile).
  • Animations and Transitions: Adding subtle visual effects.

Organizing Your CSS:

  • assets/css/style.css (Main Stylesheet): Conventionally, your primary CSS rules go in style.css.
  • Separate CSS Files (Optional): For larger themes, you might want to break down your CSS into multiple files within the assets/css/ folder for better organization (e.g., _variables.css, _typography.css, _header.css, _footer.css). You can then import these into your main style.css using @import url("_variables.css");.

Linking CSS in Your Theme:

You link your CSS stylesheet in your default.hbs template within the <head> section using the {{asset}} helper:

<link rel="stylesheet" href="{{asset "css/style.css"}}">
Enter fullscreen mode Exit fullscreen mode

CSS Best Practices for Ghost Themes:

  • Semantic CSS: Use meaningful class names that describe the content and structure rather than just appearance (e.g., .post-title, .site-header, .post-feed, not .red-text, .big-box). This makes your CSS more maintainable and understandable.
  • CSS Reset/Normalize: Consider using a CSS reset or normalize (like Normalize.css or Reset CSS) to provide a consistent baseline across browsers. Include it at the very beginning of your style.css.
  • Mobile-First Approach: Design your theme for mobile devices first and then enhance for larger screens using media queries. This ensures a good experience on smaller screens.
  • Responsiveness with Media Queries: Use CSS media queries to apply different styles based on screen size.

    /* Example media query for screens smaller than 768px (typical mobile) */
    @media screen and (max-width: 767px) {
        .site-header {
            text-align: left; /* Example style change for mobile */
        }
        /* ... other mobile-specific styles ... */
    }
    
  • CSS Variables (Custom Properties): Use CSS variables to define reusable values for colors, fonts, spacing, etc. This makes it easier to maintain consistency and make theme-wide style changes.

    :root {
        --primary-color: #007bff;
        --secondary-font: sans-serif;
        --base-spacing: 1.5rem;
    }
    
    .site-header {
        color: var(--primary-color);
        font-family: var(--secondary-font);
        margin-bottom: var(--base-spacing);
    }
    
  • CSS Frameworks (Optional): For more complex themes, you might consider using a CSS framework like Tailwind CSS or Bootstrap to speed up development and provide pre-built components. However, for simple themes, vanilla CSS is often sufficient and provides more control.

  • Minify CSS for Production: Before deploying your theme, minify your CSS files to reduce file size and improve website performance. You can use online CSS minifiers or build tools like Gulp or Grunt to automate this.

Example: Adding More Styles to style.css

Let's expand our basic style.css to make our theme look a bit nicer:

/* Updated style.css */

/* Reset/Normalize (Basic example - consider using a proper reset) */
body, html { margin: 0; padding: 0; }
body { font-family: 'Arial', sans-serif; color: #333; background-color: #f8f8f8; }

/* CSS Variables (Custom Properties) */
:root {
    --primary-color: #007bff; /* Example primary color */
    --secondary-color: #6c757d;
    --accent-color: #ffc107;
    --base-spacing: 1.5rem;
    --font-size-base: 16px;
}

/* Site Header */
.site-header {
    background-color: #fff;
    padding: var(--base-spacing) 0;
    border-bottom: 1px solid #eee;
    text-align: center;
}

.site-header h1 a {
    text-decoration: none;
    color: var(--primary-color);
    font-size: 2rem;
    font-weight: bold;
}

.site-header p {
    color: var(--secondary-color);
    font-size: var(--font-size-base);
    margin-top: 0.5rem;
}

/* Site Main Content */
.site-main {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1rem;
}

/* Post Feed (Homepage) */
.post-feed {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); /* Responsive grid */
    gap: 2rem;
    margin-bottom: 3rem;
}

.post-class { /* Base class for posts - you can add specific classes via {{post_class}} */
    background-color: #fff;
    padding: 1.5rem;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}

.post-header {
    margin-bottom: 1rem;
}

.post-title a {
    text-decoration: none;
    color: var(--primary-color);
    font-size: 1.5rem;
    font-weight: bold;
}

.post-date {
    font-size: 0.9em;
    color: var(--secondary-color);
    display: block;
    margin-top: 0.5rem;
}

.post-excerpt p {
    margin-bottom: 1.5rem;
    color: #444;
}

.read-more {
    display: inline-block;
    padding: 0.8rem 1.5rem;
    background-color: var(--accent-color);
    color: #fff;
    text-decoration: none;
    border-radius: 5px;
    transition: background-color 0.2s ease; /* Smooth transition on hover */
}

.read-more:hover {
    background-color: darken(var(--accent-color), 10%); /* Darken color on hover */
}

/* Single Post */
.post-full {
    background-color: #fff;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
    margin-bottom: 3rem;
}

.post-full-header {
    margin-bottom: 2rem;
    text-align: center;
}

.post-full-title {
    font-size: 2.5rem;
    font-weight: bold;
    color: var(--primary-color);
    margin-bottom: 1rem;
}

.post-full-meta {
    font-size: 1em;
    color: var(--secondary-color);
    margin-bottom: 2rem;
}

.post-full-meta a {
    color: var(--primary-color);
    text-decoration: none;
}

.post-full-content {
    font-size: var(--font-size-base);
    line-height: 1.8;
    color: #444;
    margin-bottom: 3rem;
}

.post-full-footer {
    border-top: 1px solid #eee;
    padding-top: 2rem;
    font-size: 0.9em;
    color: var(--secondary-color);
}

.post-tags a {
    display: inline-block;
    padding: 0.5rem 1rem;
    background-color: #eee;
    color: #444;
    text-decoration: none;
    border-radius: 4px;
    margin-right: 0.5rem;
    margin-bottom: 0.5rem;
}

/* Site Footer */
.site-footer {
    text-align: center;
    padding: 2rem 0;
    border-top: 1px solid #eee;
    color: var(--secondary-color);
    font-size: 0.9em;
}

/* Media Queries for Responsiveness */
@media screen and (max-width: 767px) { /* Mobile styles */
    .site-header {
        text-align: left;
        padding: 1rem 1rem;
    }
    .site-header h1 a { font-size: 1.8rem; }
    .site-main { margin: 1rem auto; padding: 0 0.5rem; }
    .post-feed { grid-template-columns: 1fr; } /* Stack posts on mobile */
    .post-class { padding: 1rem; }
    .post-full { padding: 1.5rem; }
    .post-full-title { font-size: 2rem; }
}
Enter fullscreen mode Exit fullscreen mode

Save style.css, restart Ghost, and refresh your blog. You'll see a much more styled theme!

Practice CSS: Experiment with adding more CSS rules to customize the layout, typography, colors, and responsiveness of your theme. Use your browser's developer tools (Inspect Element) to examine the HTML structure of your Ghost blog and identify elements to style.


7. Adding Interactivity with JavaScript

JavaScript allows you to add dynamic behavior and interactivity to your Ghost themes. You can use JavaScript for:

  • Enhancing User Experience: Adding features like image sliders, dropdown menus, smooth scrolling, lightboxes, etc.
  • Interacting with the DOM (Document Object Model): Manipulating HTML elements dynamically.
  • Fetching Data (AJAX): Getting data from external APIs or your Ghost blog's API.
  • Implementing Custom Functionality: Adding features beyond Ghost's core functionality.

Adding JavaScript to Your Theme:

  1. Create JavaScript Files: Place your JavaScript files (.js) in the assets/js/ folder of your theme. Conventionally, you might have an index.js file for your main theme JavaScript. You can create more files as needed.

  2. Link JavaScript Files in default.hbs: Link your JavaScript files in your default.hbs template right before the closing </body> tag using the {{asset}} helper:

    <script src="{{asset "js/index.js"}}"></script>
    {{ghost_foot}}
    

Example: Simple JavaScript - Console Log

Let's add a very basic JavaScript example to our assets/js/index.js to verify that JavaScript is working:

  1. Edit assets/js/index.js: Open assets/js/index.js and add the following JavaScript code:

    console.log("Hello from my-first-theme's JavaScript!");
    
    document.addEventListener('DOMContentLoaded', function() {
        console.log("DOM content loaded and parsed");
        // Your JavaScript code will go here after the page is fully loaded
    });
    
  2. Refresh your blog in the browser, and open your browser's developer console (usually by pressing F12). You should see the console messages "Hello from my-first-theme's JavaScript!" and "DOM content loaded and parsed" in the console. This confirms that your JavaScript file is being loaded and executed.

Example: Simple JavaScript - Adding a Class on Scroll (Basic Header Shrink Effect)

Let's create a slightly more visually noticeable JavaScript effect: Shrinking the header when the user scrolls down.

  1. Edit assets/js/index.js: Replace the previous content of assets/js/index.js with this code:

    document.addEventListener('DOMContentLoaded', function() {
        const header = document.querySelector('.site-header');
    
        if (header) {
            window.addEventListener('scroll', function() {
                if (window.scrollY > 50) { // Adjust scroll threshold as needed
                    header.classList.add('header-scrolled');
                } else {
                    header.classList.remove('header-scrolled');
                }
            });
        }
    });
    
  2. Add CSS for the "scrolled" header state to style.css: Add these CSS rules to your style.css:

    /* ... existing CSS ... */
    
    .site-header.header-scrolled {
        padding: 1rem 0; /* Reduced padding when scrolled */
        background-color: rgba(255, 255, 255, 0.9); /* Slightly transparent background */
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Add a subtle shadow */
        transition: padding 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition */
    }
    
    .site-header.header-scrolled h1 a {
        font-size: 1.8rem; /* Slightly smaller font size when scrolled */
    }
    
  3. Refresh your blog in the browser. Now, when you scroll down past a certain point, you should see the header shrink slightly and get a subtle shadow.

JavaScript Best Practices for Ghost Themes:

  • DOMContentLoaded Listener: Wrap your JavaScript code within a DOMContentLoaded event listener to ensure that your code runs after the HTML document is fully loaded and parsed. This prevents errors from trying to access elements that haven't been loaded yet.
  • Non-Blocking JavaScript: If you have larger JavaScript files, consider using asynchronous loading (async or defer attributes in the <script> tag) to prevent JavaScript from blocking page rendering. However, for simple theme scripts, this might not be necessary.
  • Avoid jQuery (Unless Necessary): Modern vanilla JavaScript (plain JavaScript without frameworks) is often sufficient for theme interactivity and is generally faster and lighter than jQuery. Use jQuery only if you have specific jQuery-dependent plugins or are very comfortable with it.
  • Keep it Minimal for Simple Themes: For basic blog themes, keep JavaScript focused on enhancing user experience rather than adding complex functionality. Overusing JavaScript can make your theme heavier and slower.
  • Test Thoroughly: Test your JavaScript functionality across different browsers and devices to ensure it works as expected.

Practice JavaScript: Experiment with adding more JavaScript functionality to your theme. Try:

  • Adding a back-to-top button that appears when scrolling down.
  • Implementing a simple image slider or carousel for featured posts.
  • Creating a responsive navigation menu that collapses on mobile devices.

8. Theme Settings: Making Your Theme Configurable

Theme settings allow users to customize certain aspects of your theme directly through the Ghost admin panel without needing to edit code. This makes your themes more user-friendly and adaptable.

Theme settings are defined in your theme's package.json file within the config object.

Defining Theme Settings in package.json:

Open your package.json file and add a config section like this:

{
    "name": "my-first-theme",
    "version": "0.1.0",
    "description": "A very simple Ghost theme for learning purposes.",
    "author": "Your Name",
    "engines": {
        "ghost": ">=5.0.0",
        "ghost-api": "v5"
    },
    "config": {
        "posts_per_page": {  // Setting ID (unique, lowercase, underscores)
            "type": "number", // Setting type (number, text, boolean, select, color, image)
            "default": 10,   // Default value
            "label": "Posts per Page", // Label shown in Ghost admin
            "description": "Number of posts to display on the homepage." // Optional description
        },
        "accent_color": {
            "type": "color",
            "default": "#ffc107",
            "label": "Accent Color",
            "description": "The main accent color for the theme."
        },
        "show_author_bio": {
            "type": "boolean",
            "default": true,
            "label": "Show Author Bio",
            "description": "Toggle display of author bio on single post pages."
        },
        "logo_image": {
            "type": "image",
            "label": "Logo Image",
            "description": "Upload a logo for your site."
        },
        "font_family": {
            "type": "select",
            "default": "Arial",
            "label": "Font Family",
            "description": "Choose the main font family for the theme.",
            "options": [  // Options for select type
                "Arial",
                "Helvetica",
                "Times New Roman",
                "Georgia",
                "Verdana"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting Types:

  • number: Allows users to enter a number.
  • text: Allows users to enter a single line of text.
  • boolean: Creates a checkbox (true/false).
  • select: Creates a dropdown menu with predefined options.
  • color: Provides a color picker.
  • image: Allows users to upload an image file.

Accessing Theme Settings in Handlebars Templates:

You access theme settings in your Handlebars templates using the @custom context object, followed by the setting ID (which you defined in package.json).

<style>
    :root {
        --accent-color: {{ @custom.accent_color }}; /* Access 'accent_color' setting */
    }
</style>

<header class="site-header">
    {{#if @custom.logo_image}} <img src="{{@custom.logo_image}}" alt="{{@site.title}} Logo">
    {{/if}}
    <h1><a href="{{@site.url}}">{{@site.title}}</a></h1>
</header>

{{#if @custom.show_author_bio}} <div class="author-bio">
    {{author.bio}}
</div>
{{/if}}
Enter fullscreen mode Exit fullscreen mode

How Theme Settings Appear in Ghost Admin:

  1. Restart Ghost after modifying package.json.
  2. Go to "Settings" -> "Design" -> "Theme Settings" in your Ghost admin.
  3. You will see a section for your theme ("My First Theme") with the settings you defined in package.json. Users can now adjust these settings and save them.

Updating Theme Settings in Ghost Admin: When you change theme settings in the Ghost admin and save, Ghost will automatically:

  • Store the updated settings.
  • Re-render your blog with the new settings.
  • The @custom context object in your Handlebars templates will reflect the updated setting values.

Example: Implementing Accent Color Setting

Let's implement the accent_color theme setting in our theme.

  1. Ensure accent_color setting is defined in package.json (as shown in the example above).

  2. Update style.css to use the accent_color variable:

    /* ... CSS Variables ... */
    :root {
        --primary-color: #007bff;
        --secondary-color: #6c757d;
        --accent-color: {{ @custom.accent_color }}; /* Use theme setting for accent color */
        --base-spacing: 1.5rem;
        --font-size-base: 16px;
    }
    
    /* ... rest of your CSS, using var(--accent-color) where needed ... */
    .read-more {
        background-color: var(--accent-color); /* Use accent color for "Read More" button */
        /* ... */
    }
    
    .post-title a {
        color: var(--accent-color); /* Use accent color for post titles */
    }
    /* ... other elements where you want to use accent color ... */
    

    Note: We are using {{ @custom.accent_color }} within the CSS :root to dynamically set the --accent-color CSS variable based on the theme setting.

  3. Restart Ghost, go to "Theme Settings" in admin, and you should see the "Accent Color" setting. Change the color using the color picker and save. Refresh your blog. You should see the accent color of your theme (e.g., "Read More" button, post titles) change to the color you selected in the admin panel!

Theme settings make your themes more flexible and user-friendly. Plan your theme settings carefully to provide meaningful customization options.


9. Testing and Debugging Your Theme

Testing and debugging are crucial to ensure your Ghost theme works correctly and provides a good user experience.

Testing:

  • Browser Compatibility Testing: Test your theme in different browsers (Chrome, Firefox, Safari, Edge) and browser versions to ensure cross-browser compatibility. Use browser testing tools or services if needed (e.g., BrowserStack, Sauce Labs).
  • Device Testing (Responsiveness): Test your theme on various devices (desktop, laptop, tablets, smartphones) and screen sizes to verify responsiveness. Use browser developer tools' device emulation, or ideally, test on real devices.
  • Ghost Functionality Testing: Test all core Ghost functionalities within your theme:
    • Homepage (post listings, pagination).
    • Single posts.
    • Single pages.
    • Tag archives.
    • Author archives.
    • Search functionality (if implemented).
    • Subscription functionality (if implemented).
    • Error pages (e.g., 404).
  • Performance Testing: Check your theme's performance (page load speed). Use browser developer tools (Network tab, Performance tab), website speed testing tools (Google PageSpeed Insights, WebPageTest). Optimize images, minify CSS/JavaScript, and ensure efficient code.
  • Accessibility Testing (WCAG Guidelines): Strive for accessibility. Use accessibility testing tools (Lighthouse in Chrome DevTools, WAVE browser extension) and follow WCAG (Web Content Accessibility Guidelines) principles to make your theme usable for everyone, including people with disabilities.
  • Ghost Theme Validation: Use the official Ghost Theme validation tool to check for potential issues in your theme structure and Handlebars syntax:
    • Ghost Theme Validator: https://validator.ghost.org/ (You'll need to package your theme as a .zip file to use the validator - we'll cover packaging later).

Debugging:

  • Browser Developer Tools (Inspect Element): Your primary debugging tool! Use browser developer tools (usually opened by pressing F12):
    • Elements Tab: Inspect the HTML structure and CSS styles of your page.
    • Console Tab: View JavaScript console messages, errors, and warnings. Use console.log(), console.warn(), console.error() in your JavaScript code for debugging.
    • Network Tab: Monitor network requests (loading of assets, images, etc.). Useful for identifying slow-loading resources or 404 errors.
    • Sources Tab: Debug JavaScript code, set breakpoints, step through code execution.
  • Handlebars Template Errors: If you have Handlebars syntax errors in your .hbs templates, Ghost will often display helpful error messages in the browser (usually in the admin area, and sometimes on the frontend if NODE_ENV is set to development). Pay attention to these error messages.
  • JavaScript Errors: JavaScript errors will typically be displayed in the browser's developer console (Console tab). Carefully read error messages to pinpoint the source of the problem in your JavaScript code.
  • Ghost Logs: Check Ghost server logs for more detailed error messages or server-side issues. You can usually access Ghost logs using the Ghost CLI (e.g., ghost log).
  • Start Simple and Test Incrementally: When building your theme, develop in small steps and test frequently after each change. This makes it easier to isolate and fix issues.
  • Isolate Problems: If you encounter a bug, try to isolate the problem to a specific part of your theme's code (HTML, CSS, JavaScript, Handlebars template). Comment out code sections to narrow down the source of the error.
  • Search Online: Search online (Stack Overflow, Ghost forums, Google) for error messages or similar issues. Often, someone else has encountered the same problem and found a solution.
  • Ask for Help (Community): If you are stuck, ask for help in the Ghost community forums or online developer communities. Provide clear descriptions of your problem, code snippets, and error messages.

Example Debugging Scenario:

Let's say your "Read More" links are not working on your homepage.

  1. Open browser developer tools (Inspect Element) and go to the "Console" tab. Check for any JavaScript errors. If there are JS errors, fix them in your JavaScript code.
  2. Inspect the "Read More" link element in the "Elements" tab. Verify that the <a> tag is present, has the correct href attribute (URL), and that there are no CSS styles hiding the link or preventing clicks.
  3. Check index.hbs template. Ensure you are using the {{url}} helper correctly for the "Read More" link: <a href="{{url}}" class="read-more">Read More β†’</a>. Double-check for typos or incorrect Handlebars syntax.
  4. If still not working, try simplifying the link: Temporarily remove CSS classes from the link (<a href="{{url}}">Read More</a>) to rule out CSS-related issues.
  5. If the issue persists, review the Ghost context data. Is the posts array being correctly passed to index.hbs? Are the url properties within the posts objects valid? (Less likely for core Ghost functionality, but worth considering if you are doing something more advanced).

Thorough testing and effective debugging skills are essential for creating robust and high-quality Ghost themes.


10. Packaging and Uploading Your Theme

Once you've developed, styled, tested, and debugged your Ghost theme, you need to package it for use on your Ghost blog (either locally or on a hosted Ghost instance).

Packaging Your Theme:

Ghost themes are packaged as .zip files. The zip file must contain your theme folder at the root level.

Steps to Package Your Theme:

  1. Navigate to Your Theme Folder: In your file explorer, navigate to your theme's root folder (e.g., my-first-theme). This folder should contain index.hbs, default.hbs, package.json, assets/, partials/, etc.
  2. Select All Files and Folders: Select all the files and folders within your theme's root folder (not the folder itself). This should include assets, default.hbs, index.hbs, package.json, etc.
  3. Create a Zip Archive: Right-click on the selected files and folders and choose "Compress to ZIP file" (or similar option depending on your operating system). Make sure you create a zip archive of the content of your theme folder, not of the folder itself.
  4. Rename the Zip File: The zip file should be named after your theme's folder name (e.g., my-first-theme.zip).

Uploading Your Theme to Ghost:

  1. Log in to your Ghost Admin Panel: Go to your-ghost-blog-url/ghost (or http://localhost:2368/ghost for your local instance).
  2. Navigate to "Settings" -> "Design" -> "Themes".
  3. Click the "Upload theme" button at the top right of the themes page.
  4. Choose Your .zip file: Select the .zip file you created in the packaging steps (e.g., my-first-theme.zip).
  5. Ghost will upload and install your theme. It will appear in the list of available themes.
  6. Activate Your Theme: Click the "Activate" button next to your newly uploaded theme to make it live on your blog.

Important Notes on Packaging and Uploading:

  • Root-Level Theme Folder: Ensure that your theme folder is at the root of the .zip archive, not nested inside another folder. Ghost expects to find index.hbs, package.json, etc., directly inside the zip root.
  • No Hidden Files: Make sure your zip file doesn't contain any hidden files (like .DS_Store on macOS or .Thumbs.db on Windows). These can sometimes cause issues. Clean up your theme folder before packaging.
  • Theme Validation (Optional but Recommended): Before uploading a theme intended for public distribution or production, use the Ghost Theme Validator (https://validator.ghost.org/) to check for potential issues.
  • Theme Updates: If you make changes to your theme after uploading it, you'll need to re-package the updated theme as a .zip file and re-upload it to Ghost (you can usually overwrite the existing theme).

Congratulations! You now know how to package and upload your Ghost themes!


11. Easy Example: Building a Simple Blog Theme (Step-by-Step)

Let's solidify your learning by building a very simple but functional blog theme step-by-step. We'll create a theme named "simple-blog-theme".

1. Create Theme Folder and Basic Files:

In your content/themes/ directory, create a folder named simple-blog-theme and within it, create the following files:

simple-blog-theme/
β”œβ”€β”€ assets/
β”‚   └── css/
β”‚       └── style.css
β”œβ”€β”€ default.hbs
β”œβ”€β”€ index.hbs
β”œβ”€β”€ post.hbs
β”œβ”€β”€ page.hbs
└── package.json
Enter fullscreen mode Exit fullscreen mode

2. Populate package.json:

{
    "name": "simple-blog-theme",
    "version": "0.1.0",
    "description": "A very simple blog theme for Ghost CMS.",
    "author": "Your Name",
    "engines": {
        "ghost": ">=5.0.0",
        "ghost-api": "v5"
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Populate default.hbs (Layout Template):

<!DOCTYPE html>
<html lang="{{@site.locale}}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{meta_title}}</title>
    <meta name="description" content="{{meta_description}}">
    <link rel="stylesheet" href="{{asset "css/style.css"}}">
    {{ghost_head}}
</head>
<body class="{{body_class}}">
    <div class="site-wrapper">
        <header class="site-header">
            <h1 class="site-title"><a href="{{@site.url}}">{{@site.title}}</a></h1>
        </header>

        <main class="site-main">
            {{{body}}}
        </main>

        <footer class="site-footer">
            &copy; {{date format="YYYY"}} - <a href="{{@site.url}}">{{@site.title}}</a>
        </footer>
    </div>
    {{ghost_foot}}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

4. Populate index.hbs (Homepage - Post List):

{{!< default}}

<div class="post-feed">
    {{#foreach posts}}
    <article class="post-card {{post_class}}">
        <a class="post-card-image-link" href="{{url}}">
            {{#if feature_image}}
                <img class="post-card-image" src="{{feature_image}}" alt="{{title}}" loading="lazy">
            {{/if}}
        </a>
        <div class="post-card-content">
            <a class="post-card-content-link" href="{{url}}">
                <header class="post-card-header">
                    <h2 class="post-card-title">{{title}}</h2>
                </header>
                <section class="post-card-excerpt">
                    <p>{{excerpt}}</p>
                </section>
            </a>
            <footer class="post-card-footer">
                <div class="post-card-footer-left">
                    {{#if author.profile_image}}
                        <img class="author-profile-image" src="{{author.profile_image}}" alt="{{author.name}}"/>
                    {{/if}}
                    <span>{{author.name}}</span>
                </div>
                <div class="post-card-footer-right">
                    <time datetime="{{published_at format="YYYY-MM-DD"}}">{{date format="MMM DD"}}</time>
                </div>
            </footer>
        </div>
    </article>
    {{/foreach}}
</div>

{{pagination}}
Enter fullscreen mode Exit fullscreen mode

5. Populate post.hbs (Single Post):

{{!< default}}

<article class="post-full {{post_class}}">
    <header class="post-full-header">
        <h1 class="post-full-title">{{title}}</h1>
        <div class="post-full-byline">
            <section class="post-full-byline-content">
                <ul class="author-list">
                    <li class="author-list-item">
                        {{#if author.profile_image}}
                        <div class="author-profile-image-wrapper">
                            <img class="author-profile-image" src="{{author.profile_image}}" alt="{{author.name}}" loading="lazy" />
                        </div>
                        {{/if}}
                        <div class="author-details">
                            <a href="{{author.url}}" class="author-link">{{author.name}}</a>
                        </div>
                    </li>
                </ul>
                <time class="post-full-meta-date" datetime="{{published_at format="YYYY-MM-DD"}}">{{date format="MMM DD, YYYY"}}</time>
            </section>
        </div>
    </header>

    {{#if feature_image}}
    <figure class="post-full-image">
        <img src="{{feature_image}}" alt="{{title}}" loading="lazy">
    </figure>
    {{/if}}

    <section class="post-full-content">
        {{content}}
    </section>

    <footer class="post-full-footer">
        {{#if tags}}
        <div class="post-tags">
            Tags: {{tags separator=" "}}
        </div>
        {{/if}}
    </footer>
</article>
Enter fullscreen mode Exit fullscreen mode

6. Populate page.hbs (Single Page):

{{!< default}}

<article class="page-content">
    <header class="page-header">
        <h1 class="page-title">{{title}}</h1>
    </header>

    <section class="page-body">
        {{content}}
    </section>
</article>
Enter fullscreen mode Exit fullscreen mode

7. Populate assets/css/style.css (Basic Styles - you can expand this greatly!):

/* Simple Blog Theme CSS */

body {
    font-family: sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
    color: #333;
    line-height: 1.6;
}

.site-wrapper {
    max-width: 960px;
    margin: 0 auto;
    padding: 20px;
}

.site-header {
    padding: 20px 0;
    text-align: center;
}

.site-title a {
    text-decoration: none;
    color: #222;
    font-size: 2em;
    font-weight: bold;
}

.site-main {
    margin-top: 30px;
}

.post-feed {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 20px;
}

.post-card {
    background-color: #fff;
    border-radius: 5px;
    overflow: hidden;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.post-card-image-link {
    display: block;
    overflow: hidden;
}

.post-card-image {
    width: 100%;
    height: auto;
    display: block;
}

.post-card-content {
    padding: 20px;
}

.post-card-title {
    font-size: 1.5em;
    margin-bottom: 10px;
}

.post-card-excerpt p {
    margin-bottom: 0;
    color: #555;
}

.post-card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 20px;
    border-top: 1px solid #eee;
    padding-top: 10px;
}

.post-card-footer-left {
    display: flex;
    align-items: center;
}

.author-profile-image {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    margin-right: 10px;
}

.post-card-footer-right time {
    font-size: 0.9em;
    color: #777;
}

.post-full {
    background-color: #fff;
    padding: 30px;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.post-full-header {
    text-align: center;
    margin-bottom: 30px;
}

.post-full-title {
    font-size: 2.5em;
    margin-bottom: 10px;
}

.post-full-byline {
    margin-bottom: 20px;
    text-align: center;
}

.author-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: inline-block;
}

.author-list-item {
    display: inline-flex;
    align-items: center;
}

.author-profile-image-wrapper {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    overflow: hidden;
    margin-right: 10px;
}

.author-profile-image {
    width: 100%;
    height: auto;
    display: block;
}

.post-full-meta-date {
    font-size: 0.9em;
    color: #777;
}

.post-full-image {
    margin-bottom: 30px;
}

.post-full-image img {
    width: 100%;
    height: auto;
    display: block;
}

.post-full-content {
    line-height: 1.8;
}

.post-full-footer {
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid #eee;
}

.post-tags a {
    display: inline-block;
    padding: 5px 10px;
    background-color: #eee;
    color: #444;
    text-decoration: none;
    border-radius: 3px;
    margin-right: 5px;
}

.page-content {
    background-color: #fff;
    padding: 30px;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.page-header {
    text-align: center;
    margin-bottom: 20px;
}

.page-title {
    font-size: 2em;
    margin-bottom: 10px;
}

.page-body {
    line-height: 1.8;
}

.site-footer {
    text-align: center;
    padding: 30px 0;
    border-top: 1px solid #eee;
    color: #777;
    font-size: 0.9em;
}
Enter fullscreen mode Exit fullscreen mode

8. Activate "Simple Blog Theme" in Ghost Admin:

Restart Ghost, go to "Settings" -> "Design" -> "Themes", and activate "Simple Blog Theme".

9. View Your Simple Blog Theme!

Visit your blog's homepage (http://localhost:2368). You should now see your simple blog theme displaying your posts in a card layout. View a single post to see the single post template.

Expand and Customize!

This is a very basic theme example. You can now:

  • Add more CSS to style.css to further style your theme.
  • Create more Handlebars templates (e.g., tag.hbs, author.hbs, error.hbs).
  • Add JavaScript interactivity to assets/js/index.js.
  • Implement theme settings in package.json to make your theme configurable.
  • Add partials in the partials/ folder for reusable template snippets.

This example should give you a solid foundation to continue building more complex and personalized Ghost themes.


12. Further Learning and Resources

Congratulations on making it through this full guide! You've learned the fundamentals of Ghost CMS theme development. To deepen your knowledge and continue your journey, here are valuable resources:

Next Steps:

  • Practice, Practice, Practice: The best way to learn is by doing. Start building more themes, experiment with different features, and try to recreate designs you like.
  • Study Existing Themes: Examine the code of existing Ghost themes (like Casper or other open-source themes) to learn best practices and advanced techniques.
  • Contribute to Open Source: Consider contributing to open-source Ghost themes or creating and sharing your own themes with the community.
  • Stay Updated: Ghost CMS is constantly evolving. Keep an eye on Ghost's official blog and documentation for updates, new features, and best practices related to theme development.

You are now well-equipped to embark on your Ghost theme development journey! Have fun creating amazing themes for your Ghost blogs! πŸŽ‰

Top comments (0)