How to create a theme for Ghost

The blogging platform Ghost has been getting a lot of attention recently due to its recent move from beta to public release (if you somehow haven’t heard of it yet, head over here and read up).

Like other platforms, Ghost supports third party ‘themes’. In this article we’ll design a simple, responsive and content-driven blog. Then code it into a fully-functional theme for Ghost.

 

The design

The default theme for Ghost, ‘Casper’, is very clean looking. They put content first, showing off the typography with a modern color palette, so that the focus is on the writing.

We’re going to follow that lead, so our design process needs to start by understanding how Ghost works and what’s available to the user in the back-end. There are 4 main elements that we’ll be able to draw upon from the back-end while designing the theme (apart from the articles/posts themselves obviously) which are:

  • Blog title
  • Blog description
  • Blog logo
  • Blog cover

All of which can be set in the Settings tab in Ghost. These point the design in an obvious direction of some sort of banner at the top of the page, which contains the title, logo and description and a cover picture as a background.

The home page

So we really only have to design 2 pages, that is the home page, which shows all of the latest posts, and the individual post page. As the design is relatively simple, I’ll show the finished page first, then go over the details again. So here is the “home” page which displays the latest posts:

1

So as you can see, colourful highlights, along with basic and clean design. Let’s run over the details again. So we’ve got the header, that contains the logo (here I’ve made a little sheet costume for a ghost), the name of the blog and the description.

2

So if the user chooses a cover photo we’ll put it in as a full-width background image here. If not we’ll go for a solid blue color that’ll be our highlight color as above.

6

Then we’ve got some content boxes in which we’ll show all the information about each post (title, published date, author, tags) and the excerpt.

3

Finally we make a simple pagination link and a footer. In the article share box, the footer and all throughout the site we’re using a custom icon font made at Fontello and the font Open Sans from Google Web Fonts. Which we’ll see how to implement later.

4

The individual post page

This design is very similar to the home page. Except the block in which we enclosed the excerpt before will now stretch full-height and show all the content. Plus we’ll add an author box at the bottom.

5

So everything the same, apart from some inline text styling which we’ll go over in the development stage. And here’s the new author box:

7

 

The development

Okay, so now the design has been looked over (and obviously customize it to your own preferences). It’s time to start on the development. First of all, if you haven’t already done so, take a second and read the official Ghost documentation on theme creation. They are very clear and concise about what’s needed and file structure and so on. Basically for this tutorial, we can split the development into two stages. Content and style. Like a basic relationship between HTML and CSS, we’ll make the theme work, then make it look like our design.

The file structure

To begin you’ll need to have Ghost installed locally on your machine. This is relatively straight forward to do, and there are now even automatic installers (like this one). Once it’s installed and running, you’ll need to find the ghost folder which is named ‘ghost-version.number’ (at the time of writing it is ‘ghost-0.3.2′). Once located, navigate to ‘content/themes’,  there you’ll want to create a new folder that has the name of your theme. So in this case we’ll call it ‘sheet’. Inside that folder, we’re going to make 2 files that are essential to a Ghost theme. That’s ‘index.hbs’ and ‘post.hbs’, if you’ve read the Ghost documentation (or have used Handlebars elsewhere) you’ll recognise the file format ‘.hbs’ which means we can use the so called ‘handlebars': {{}} in our template.

After that, we’ll add another file called ‘default.hbs’ and a folder structure for our assets and partial files. Follow this file structure:

8

At ‘assets/css/fonts’ we’ll place our icon font files for the @font-face implementation. For this design I’ve only chosen 6 icons: Facebook, Twitter, Google, RSS, Tags, Calendar.

9

Apart from that the rest of the files are pretty self-explanatory. Now let’s get into the code. We’ll look first at the two essential files in the theme.

“index.hbs” & “post.hbs”

As for the designs, I’ll give the whole contents of the file first, then we’ll break up the important bits. First of all the ‘index.hbs’ file:

{{!< default}}
{{> header}}
<main id="main" role="main">
{{#foreach posts}}
<article role="article" id="article-container">
<header>
<h1><a href="{{{url}}}">{{title}}</a></h1>
<p>by <a href="{{author.website}}" target="_blank">{{author}}</a></p>
<time datetime="{{date format="DD MMM YYYY"}}">
<span></span> 
{{date format='DD MMM YYYY'}}
</time>
</header>
<div>
<p>{{excerpt words="100"}} &hellip; <a href="{{{url}}}">Read More</a></p>
</div>
</article>
<footer>
 {{#if tags}}
<div>
<span></span> Tags: {{tags separator=" . "}}
</div>
{{/if}}
<div>
 <p>Share:</p> 
 <a href="http://www.facebook.com/sharer.php?u={{url absolute="true"}}" target="_blank"><span></span></a> 
 <a href="http://twitter.com/share?text={{title}}&url={{url absolute="true"}}" target="_blank"><span></span></a> 
 <a href="https://plus.google.com/share?url={{url absolute="true"}}" target="_blank"><span></span></a>
</div>
</footer>
{{/foreach}}
{{#if pagination}}
<div>
{{{pagination}}}
</div>
{{/if}}
</main>
{{> footer}}

So to explain what’s happening, the very first line

{{!< default}}

is essential to the theme and tell us that this file will be inserted into the content area into the default file. The second line

{{> header}}

then inserts our header. Instead of putting it into this file we’ll create a partial file later in the ‘partials’ directory. HBS files are just like HTML files in which you can include normal HTML elements. So the next element in the file is a <main> which will hold all of our main content. Then inside of the

{{#foreach posts}}

repeater we’ll put our article element that will contain the post. Inside each <article> there will be a <header> with the title and the meta data of the article (author and publish date). After that the actual excerpt. Then a <footer> that has our post’s tags (in between an ‘if’ statement) and the share links, with some span’s calling in our icon font. Finally we have some pagination, and we eventually call in the

{{> footer}}

which works like the header. So as you can see it’s relatively simple code. Obviously any class or ID names are optional for CSS styling.

Next up comes the ‘post.hbs’ file. Which is mostly similar, but with a few additions and modifications.

{{!< default}}
{{> header}}
<main id="main" role="main">
{{#post}}
<a href="/">&larr; Go Back</a>
<article role="article" id="article-container">
  <header>
 <h1><a href="{{{url}}}">{{title}}</a></h1>
 <p>by <a href="{{author.website}}" target="_blank">{{author}}</a></p>
 <time datetime="{{date format="DD MMM YYYY"}}">
 <span></span> 
 {{date format='DD MMM YYYY'}}
 </time>
</header>
  <div>
    {{content}}
  </div>
  {{#if tags}}
<div>
<span></span> Tags: {{tags separator=" . "}}
</div>
{{/if}}
<div>
 <p>Share: 
 <a href="http://www.facebook.com/sharer.php?u={{url absolute="true"}}" target="_blank"><span></span></a> 
 <a href="http://twitter.com/share?text={{title}}&url={{url absolute="true"}}" target="_blank"><span></span></a> 
 <a href="https://plus.google.com/share?url={{url absolute="true"}}" target="_blank"><span></span></a>
 </p>
</div>
</article>
{{#if author}}
<footer>
<h4>About the Author</h4>
<div>
  {{#if author.image}}
  <div><img src="{{author.image}}" /></div>
  {{/if}}
  <h4>{{author.name}}</h4>
  {{#if author.website}}
  <div><a href="{{author.website}}" target="_blank">{{author.website}}</a></div>
  {{/if}}
  <div>
  <p>{{author.bio}}</p>
  </div>
</div>
</footer>
{{/if}}
{{/post}}
</main>
{{> footer}}

Once again we have the

{{!< default}}

and

{{> header}}

handlebars. And our <main> element. But this time the wrapping handlebar is

{{#post}}

Then we call in the

{{content}}

(Not excerpt, because this is the individual page we want to show all of the content in the post.) Another difference here is the ‘if’ statement which is verifying if there’s an author for the post. If there is we’ll show a box with the author’s image, name, URL and bio. All of these elements are editable in the back-end by the user, and will either show up or not depending on whether they exist.

‘default.hbs’ and the partial files

Now we’re going to look at the default.hbs file. So if you haven’t made it yet, create a new file. In that file we’re going to put all the default things that are needed for our page to display correctly in a browser, mainly the doctype and the <html>, <head> and<body> tags.

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="UTF-8" />
 
    <title>{{meta_title}}</title>
    <meta name="description" content="{{meta_description}}" />
    
    <link rel="stylesheet" type="text/css" href="/assets/css/style.css" />
<link href='http://fonts.googleapis.com/css?family=Droid+Serif|Open+Sans:400italic,600italic,700italic,800italic,400,600,700,800' rel='stylesheet' type='text/css'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     
    <!--[if lt IE 9]>
    <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]--> 
    {{ghost_head}}
</head>
<body>
 <div>
    {{{body}}}
 </div>
 {{ghost_foot}}
</body>
</html>

So looks like a normal HTML document. In the <head> we’re pulling in the title and the description from the blog. We’re linking to our stylesheet in our assets folder, as well as linking to our Google web fonts. We’re adding the viewport meta to make sure the site will be responsive on all devices, and finally the HTML5 shiv for IE. Note also the {{ghost_head}} tag at the end. These are essential to the ghost theme’s functionality, like the wp_head() in WordPress. (The ‘{{ghost_foot}} before the closing body tag does the same thing).

Then inside our body and wrapper divs we simply add the

{{{body}}}

handlebar which then inserts either the ‘index.hbs’ file contents, or the ‘post.hbs’ file contents according to where we are on the site.

Now for the partial documents files. Inside our ‘partials’ directory, we have two files. ‘header.hbs’ and ‘footer.hbs’. Thet are both relatively simple as you’ll see. Here they are, first the header:

<div role='banner' class="main-header" {{#if @blog.cover}}style="background: url({{@blog.cover}});"{{/if}}>
{{#if @blog.logo}}<div class="logo-container"><a class="logo" href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a></div>{{/if}}
<h1 class="blogtitle"><a title="{{@blog.title}}" href='{{@blog.url}}'>{{{@blog.title}}}</a></h1>
<h2 class="blogdescription">{{@blog.description}}</h2>
</div>

And now the footer:

<footer>
<div>
<a href="http://www.facebook.com/sharer.php?u={{url absolute="true"}}" target="_blank"><span></span></a> 
<a href="http://twitter.com/share?text={{@blog.title}}&url={{url absolute="true"}}" target="_blank"><span></span></a> 
<a href="https://plus.google.com/share?url={{url absolute="true"}}" target="_blank"><span></span></a>
<a target="_blank" href="{{@blog.url}}/rss/"><span><span></span></span></a>
</div>
<p>&copy; {{date format='YYYY'}} <a href="{{@blog.url}}">{{@blog.title}}</a> - All Rights Reserved.</p>
<p>Powered by <a href="http://ghost.org" target="_blank">Ghost</a></p>
</footer>

Both of these are relatively simple. In the header, we’re first of all checking if the user has added a cover photo for their blog, if so, then it’s added as the background-image on the header div. Then if there’s a logo, we’ll put that there. Then the title, and finally the description. All things that are modifiable by the user. Then in the footer, We have the same social link structure as we do in the ‘share’ section of the article container. Then some copyright text with the current year, and the blog’s title. Then a link to Ghost.

So that’s it for the theme functionality! If you now restart your Ghost server, the theme will appear in the Settings window and you can activate it. Then if you re-load your blog. It’ll look something like this:

10

 

The styles

Now it’s time to jump into the CSS and make it look like our design.
I’m going to give all of the css first so you can copy directly or use as you wish. Then run over the most important aspects, as a lot of the styling is very simple and doesn’t really need explaining.

@import "reset.css";
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
}
img {
max-width: 100%;
height: auto;
width: auto\9;
/* ie8 */ }
.cf:before,
.cf:after {
content: " ";
/* 1 */
display: table;
/* 2 */ }
.cf:after {
clear: both; }
@font-face {
font-family: 'sheet';
src: url('fonts/sheet.eot?85866638');
src: url('fonts/sheet.eot?85866638#iefix') format('embedded-opentype'),
url('fonts/sheet.woff?85866638') format('woff'),
url('fonts/sheet.ttf?85866638') format('truetype'),
url('fonts/sheet.svg?85866638#sheet') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "sheet";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
}
.icon-tags:before { content: '\54'; } /* 'T' */
.icon-calendar:before { content: '\63'; } /* 'c' */
.icon-google:before { content: '\67'; } /* 'g' */
.icon-rss:before { content: '\72'; } /* 'r' */
.icon-twitter:before { content: '\74'; } /* 't' */
.icon-facebook:before { content: '\66'; } /* 'f' */
/* General Styles */
body {
background: #f4f4f4;
color: #4e4e4e;
font-family: 'Open Sans', sans-serif; }
.main-header {
position: relative;
overflow: hidden;
padding: 50px 0 80px 0;
background-size: cover !important;
background-color: #2bcbc8; }
@media (max-width: 650px) {
.main-header {
padding: 25px 0 30px 0; } }
.main-header .logo-container {
display: block;
width: 100%;
text-align: center; }
.main-header a.logo {
display: inline-block;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
-ms-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease; }
.main-header a.logo:hover {
opacity: 0.8;
}
.main-header .blogtitle {
text-align: center;
width: 100%;
display: block; }
.main-header .blogtitle a {
display: inline-block;
color: white;
margin: 10px 0;
padding: 5px;
font-size: 65px;
font-weight: bold;
text-transform: uppercase;
border: 9px solid white;
text-decoration: none;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
-ms-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease; }
.main-header .blogtitle a:hover {
color: rgba(255,255,255,0.8);
border-color: rgba(255,255,255,0.8);
}
@media (max-width: 650px) {
.main-header .blogtitle a {
font-size: 0.8em;
border: 5px solid white; } }
.main-header h2.blogdescription {
text-align: center;
width: 100%;
display: block;
color: #fff;
margin: 0;
padding: 5px;
font-size: 36px;
font-family: 'Droid Serif', serif;
text-decoration: none; }
@media (max-width: 1000px) {
.main-header h2.blogdescription {
font-size: 1.5em; } }
@media (max-width: 650px) {
.main-header h2.blogdescription {
font-size: 1em; } }
a {
color: #2bcbc8;
text-decoration: none;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
-ms-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease;
}
a:hover {
color: #124140;
}
#main {
max-width: 1000px;
margin: 0 auto 40px auto;
padding: 30px 50px;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
-ms-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease; }
@media (max-width: 650px) {
#main {
padding: 20px 10px; } }
#main #article-container {
margin: 0 0 0 0;
padding: 30px 45px;
background: #fff;
border: 1px solid #D4DBDB;
-webkit-border-top-left-radius: 3px;
-webkit-border-top-right-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-topright: 3px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
@media (max-width: 650px) {
#main #article-container {
padding: 20px; } }
#main footer {
background: #4f4f4f;
margin-bottom: 50px;
padding: 20px;
color: white;
-webkit-border-bottom-left-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-radius-bottomleft: 3px;
-moz-border-radius-bottomright: 3px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.posttags {
font-weight: 600;
font-size: 13px;
font-style: italic;
float: left;
margin-top: 5px;
}
.index-post-share {
float: right;
font-weight: 600;
font-size: 13px;
}
@media (max-width: 650px) {
.index-post-share {
width: 100%; margin: 15px 0 0 0;
float: none;
display: inline-block;
}
}
.index-post-share p {
margin-right: 10px;
display: inline-block;
}
.index-post-share a {
font-size: 11px;
color: #4f4f4f;
background: #f4f4f4;
display: inline-block;
padding: 5px;
max-width: 22px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.index-post-share a:hover {
background-color: #2bcbc8;
}
.post-template .article {
border-bottom: none; }
.posttitle {
font-size: 60px;
font-weight: bold;
line-height: 70px;
margin-bottom: 10px; }
@media (max-width: 650px) {
.posttitle {
font-size: 30px;
line-height: 35px; } }
.postauthor {
margin-top: -10px;
}
.postheader {
margin: 0 0 25px 0;
color: #a9a9a9;
}
.postheader h1 a {
text-decoration: none; }
@media (max-width: 650px) {
.postheader h1 a {
font-size: 25px;
}
}
.postauthor, .postdate {
font-weight: 600;
font-size: 13px;
margin-bottom: 3px;
}
.postexcerpt {
font-size: 14px;
line-height: 24px;
font-weight: 600;
}
.pagination {
text-align: center;
font-weight: 600;
font-size: 13px;
}
.older-posts, .newer-posts {
margin: 0 10px;
}
.go-back {
display: inline-block;
margin: 0 0 15px 0;
}
.main-footer {
background: #4f4f4f;
color: white;
width: 100%;
padding: 30px 0 20px 0;
text-align: center;
}
.main-footer .footertext {
font-weight: 600;
font-size: 11px;
margin: 10px 0;
}
.main-footer a:hover {
color: #34fdfd;
}
.main-footer .social-links a {
font-size: 18px;
color: #4f4f4f;
background: #f4f4f4;
display: inline-block;
padding: 5px;
max-width: 30px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.main-footer .social-links a:hover {
background: #34fdfd;
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
margin-top: 25px;
margin-bottom: 5px;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2.5em; }
h3 {
font-size: 2em; }
h4 {
font-size: 1.5em; }
h5 {
font-size: 1em; }
h6 {
font-size: 0.8em; }
.postcontent {
margin: 0 0 70px 0;
}
.postcontent p {
margin-top: 5px;
font-size: 14px;
line-height: 24px;
}
.postcontent ul {
list-style: disc;
}
.postcontent ul ul {
list-style: circle;
}
.postcontent ol {
list-style: decimal;
}
.postcontent ol li {
margin: 5px 0;
}
.postcontent ul li, .postcontent ul ul li {
margin: 5px 0;
}
.postcontent ul, .postcontent ol {
margin: 0 0 20px 25px; }
.postcontent ul ul, .postcontent ul ol, .postcontent ol ul, .postcontent ol ol {
margin: 5px 0 5px 25px; }
.postcontent blockquote {
display: block;
border-left: 5px solid #02c9c9;
padding-left: 25px;
margin: 10px 0;
font-style: italic; }
.postcontent pre {
background: #4f4f4f;
border: 1px solid #b3b3b3;
padding: 20px;
color: white;
line-height: 20px;
margin-bot-tom: 20px; }
.postcontent pre code {
border: none; }
.postcontent input {
padding: 5px; }
.postcontent p code {
background: #4f4f4f;
}
.postcontent code {
color: white;
border: 1px solid #b3b3b3; }
.postcontent hr {
border: none;
border-bottom: 1px solid #D4DBDB;
margin: 20px auto;
}
.postcontent p em, .postcontent p i {
font-style: italic;
}
.postcontent p b, .postcontent p strong {
font-weight: bold;
}
.icon-feed {
font-size: 30px; }
.sharepost {
margin: 0 auto 20px auto;
width: 220px;
text-align: center;
padding: 15px; }
.sharepost .share {
font-size: 30px; }
.post-template #main footer {
padding: 20px 50px;
margin-bottom: 0;
}
@media (max-width: 650px) {
.post-template #main footer {
padding: 20px 25px; }
}
.authorbox h4:first-child {
margin-top: 0;
margin-bottom: 20px;
font-size: 24px;
}
.authorbox a:hover {
color: #34fdfd;
}
.authorbox .authorimage {
max-width: 110px;
float: left;
margin-right: 20px;
}
.authorbox .authorbio {
margin-top: 10px;
line-height: 24px;
font-size: 14px;
font-weight: 600;
}

Okay, so first things first, I’m importing the css reset from here which is also in our ‘css’ folder, to start from a clean base. After that are some general styles we set up that are site-wide and useful for responsive design. As well a clearfix by pseudo element solution applied to the class name ‘cf’.

Then we have the @font-face and icon-* settings, which sets up our icon font for us.

(This code is actually coming out of a SASS document which I haven’t included here for the sake of simplicity.)

After that all the other styles are very self-explanatory, we’re just using the class-names that we specified during the theming of the .hbs files. And applying basic styles to them.

And now if we go back to our Ghost server and that ugly blank HTML content page we’ll see something quite different:

11
12
13

That’s it! We now have a fully functional Ghost theme. Jump over to the back end and make a few dummy posts to test how pagination works etc. And then you’re ready to get blogging or even design and sell your own themes!

Have you started using Ghost? Are you planning to theme it yourself? Let us know in the comments.

0 shares
  • Jacky Weston

    Good luck with Ghost, have you seen what it takes to just get it up and running? Plus the fact that it will not run on Shared hosting disqualifies 90% of the people out there that would want to even use it. No wonder they’re creating a hosted version. It’s the only way it will survive.

    • http://www.webdesignerdepot.com/ Benjie — WebdesignerDepot

      There are automated installers available if you struggle with installations. But actually, it’s not too bad.

      Of course it’s unfortunate that shared hosting tends not to support it, but virtual servers are extremely affordable these days. WordPress’ highly-convenient one-click install didn’t arrive fully formed, it had to be adopted. I’d be very surprised if Ghost isn’t being offered on shared hosting in the next couple of months.

      90% of the people who want to use it will be the same people who use WordPress for blogging and they tend to prefer the hassle-free hosted solution anyway.

    • Navin Nagpal

      Yes Ghost looks too interesting and sophisticated. But yes as the months pass, we will see better installation procedure and easier customization in the future versions I hope.

      What we would also need is dedicated and resourceful sites for ghost to learn better just as there are for WordPress. I came across http://discoverghost.com/. Are you guys here aware of any more ?

    • Skelinger

      I know this is a old post but FYI I registered a free openshift.com account and installed ghost and deployed it with a single shell command.

  • http://npmk.com.ar Nehuén Mingote Kisler

    Great post! I’ll try this when I get back home!

    • harryjatkins

      Thanks! Glad you like it!

  • http://twitter.com/_pof_ Preston Fitzgerald

    Can you clarify this for me? I’m just curious. Different than what? WordPress? I’d say they succeeded there.

  • chiropractor melbourne

    wow! This is amazing tutorial so much for learn:

  • B.C. Bailey

    Thanks for that excellent and informative post—it comes at an excellent time. I’ve installed Ghost and began experimenting with it, it is an awesome platform. I plan on starting my own blog on it soon.

  • http://www.lcddisplayscreens.co.uk/ aredey

    Very nice work. Thanks for sharing this information. This is very useful for everyone. You done an awesome job.

  • Noah

    Can you download this theme, looks great;)

  • http://magino.me/blog Antonino Venuto

    wow…great tutorial ^___^

  • Cody

    Thank you! Nice and detailed. I appreciate you taking the time to lay this out.