Canonical Voices

Ghost

Welcome to Ghost

Welcome to Ghost

Read more
Ghost

Writing posts with Ghost ✍️

Ghost has a powerful visual editor with familiar formatting options, as well as the ability to seamlessly add dynamic content.

Select the text to add formatting, headers or create links, or use Markdown shortcuts to do the work for you - if that's your thing.

Writing posts with Ghost ✍️

Rich editing at your fingertips

The editor can also handle rich media objects, called cards.

You can insert a card either by clicking the  +  button on a new line, or typing  /  on a new line to search for a particular card. This allows you to efficiently insert images, markdown, html and embeds.

For Example:

  • Insert a video from YouTube directly into your content by pasting the URL
  • Create unique content like a button or content opt-in using the HTML card
  • Need to share some code? Embed code blocks directly
<header class="site-header outer">
    <div class="inner">
        {{> "site-nav"}}
    </div>
</header>

Working with images in posts

You can add images to your posts in many ways:

  • Upload from your computer
  • Click and drag an image into the browser
  • Paste directly into the editor from your clipboard
  • Insert using a URL

Once inserted you can blend images beautifully into your content at different sizes and add captions wherever needed.

Writing posts with Ghost ✍️

The post settings menu and publishing options can be found in the top right hand corner. For more advanced tips on post settings check out the publishing options post!

Read more
Ghost

Publishing options

Publishing options

The Ghost editor has everything you need to fully optimise your content. This is where you can add tags and authors, feature a post, or turn a post into a page.

Access the post settings menu in the top right hand corner of the editor.

Post feature image

Insert your post feature image from the very top of the post settings menu. Consider resizing or optimising your image first to ensure it's an appropriate size.

Structured data & SEO

Customise your social media sharing cards for Facebook and Twitter, enabling you to add custom images, titles and descriptions for social media.

There’s no need to hard code your meta data. You can set your meta title and description using the post settings tool, which has a handy character guide and SERP preview.

Ghost will automatically implement structured data for your publication using JSON-LD to further optimise your content.

{
    "@context": "https://schema.org",
    "@type": "Article",
    "publisher": {
        "@type": "Organization",
        "name": "Publishing options",
        "logo": "https://static.ghost.org/ghost-logo.svg"
    },
    "author": {
        "@type": "Person",
        "name": "Ghost",
        "url": "http://demo.ghost.io/author/ghost/",
        "sameAs": []
    },
    "headline": "Publishing options",
    "url": "http://demo.ghost.io/publishing-options",
    "datePublished": "2018-08-08T11:44:00.000Z",
    "dateModified": "2018-08-09T12:06:21.000Z",
    "keywords": "Getting Started",
    "description": "The Ghost editor has everything you need to fully optimise your content. This is where you can add tags and authors, feature a post, or turn a post into a page.",
    }
}
    

You can test that the structured data schema on your site is working as it should using Google’s structured data tool.

Code Injection

This tool allows you to inject code on a per post or page basis, or across your entire site. This means you can modify CSS, add unique tracking codes, or add other scripts to the head or foot of your publication without making edits to your theme files.

To add code site-wide, use the code injection tool in the main admin menu. This is useful for adding a Facebook Pixel, a Google Analytics tracking code, or to start tracking with any other analytics tool.

To add code to a post or page, use the code injection tool within the post settings menu. This is useful if you want to add art direction, scripts or styles that are only applicable to one post or page.

From here, you might be interested in managing some more specific admin settings!

Read more
Ghost

Managing admin settings

Managing admin settings

There are a couple of things to do next while you're getting set up:

Make your site private

If you've got a publication that you don't want the world to see yet because it's not ready to launch, you can hide your Ghost site behind a basic shared pass-phrase.

You can toggle this preference on at the bottom of Ghost's General Settings:

Managing admin settings

Ghost will give you a short, randomly generated pass-phrase which you can share with anyone who needs access to the site while you're working on it. While this setting is enabled, all search engine optimisation features will be switched off to help keep your site under the radar.

Do remember though, this is not secure authentication. You shouldn't rely on this feature for protecting important private data. It's just a simple, shared pass-phrase for some very basic privacy.


Invite your team

Ghost has a number of different user roles for your team:

Contributors
This is the base user level in Ghost. Contributors can create and edit their own draft posts, but they are unable to edit drafts of others or publish posts. Contributors are untrusted users with the most basic access to your publication.

Authors
Authors are the 2nd user level in Ghost. Authors can write, edit  and publish their own posts. Authors are trusted users. If you don't trust users to be allowed to publish their own posts, they should be set as Contributors.

Editors
Editors are the 3rd user level in Ghost. Editors can do everything that an Author can do, but they can also edit and publish the posts of others - as well as their own. Editors can also invite new Contributors+Authors to the site.

Administrators
The top user level in Ghost is Administrator. Again, administrators can do everything that Authors and Editors can do, but they can also edit all site settings and data, not just content. Additionally, administrators have full access to invite, manage or remove any other user of the site.

The Owner
There is only ever one owner of a Ghost site. The owner is a special user which has all the same permissions as an Administrator, but with two exceptions: The Owner can never be deleted. And in some circumstances the owner will have access to additional special settings if applicable. For example: billing details, if using Ghost(Pro).

It's a good idea to ask all of your users to fill out their user profiles, including bio and social links. These will populate rich structured data for posts and generally create more opportunities for themes to fully populate their design.

Next up: Organising your content

Read more
Ghost

Organising your content

Organising your content

Ghost has a flexible organisational taxonomy called tags which can be used to configure your site structure using dynamic routing.

Basic Tagging

You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.

When you create content for your publication you can assign tags to help differentiate between categories of content.

For example you may tag some content with  News and other content with Podcast, which would create two distinct categories of content listed on /tag/news/ and /tag/weather/, respectively.

If you tag a post with both News and Weather - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.

The primary tag

Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default.

News, Technology, Startup

So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.

Private tags

Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.

For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.

News, #video

Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting.

You can find documentation for theme development techniques like this and many more over on Ghost's extensive theme documentation.

Dynamic Routing

Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates.

For example, you may not want content tagged with News to exist on: example.com/tag/news. Instead, you want it to exist on example.com/news .

In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.

There are lots of use cases for dynamic routing with Ghost, here are a few common examples:

  • Setting a custom home page with its own template
  • Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content
  • Creating a founders column as a unique view, by filtering content created by specific authors
  • Including dates in permalinks for your posts
  • Setting posts to have a URL relative to their primary tag like example.com/europe/story-title/
Dynamic routing can be configured in Ghost using YAML files. Read our dynamic routing documentation for further details.

You can further customise your site using Apps & Integrations.

Read more
Ghost

Apps & integrations

Apps & integrations

There are three primary ways to work with third-party services in Ghost: using Zapier, editing your theme, or using the Ghost API.

Zapier

You can connect your Ghost site to over 1,000 external services using the official integration with Zapier.

Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.

Example: When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).

Here are the most popular Ghost<>Zapier automation templates:

Editing your theme

One of the biggest advantages of using Ghost over centralised platforms is that you have total control over the front end of your site. Either customise your existing theme, or create a new theme from scratch with our Theme SDK.

You can integrate any front end code into a Ghost theme without restriction, and it will work just fine. No restrictions!

Here are some common examples:

  • Include comments on a Ghost blog with Disqus or Discourse
  • Implement MathJAX with a little bit of JavaScript
  • Add syntax highlighting to your code snippets using Prism.js
  • Integrate any dynamic forms from Google or Typeform to capture data
  • Just about anything which uses JavaScript, APIs and Markup.

Using the Public API

Ghost itself is driven by a set of core APIs, and so you can access the Public Ghost JSON API from external webpages or applications in order to pull data and display it in other places.

The Ghost API is thoroughly documented and straightforward to work with for developers of almost any level.

Alright, the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, here are some more details on how that works.

Read more
Ghost

Creating a custom theme

Creating a custom theme

Ghost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.

There are a huge range of both free and premium pre-built themes which you can get from the Ghost Theme Marketplace, or you can create your own from scratch.

Creating a custom theme
Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS

Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: {{author.name}} outputs the name of the current author.

The best way to learn how to write your own Ghost theme is to have a look at the source code for Casper, which is heavily commented and should give you a sense of how everything fits together.

  • default.hbs is the main template file, all contexts will load inside this file unless specifically told to use a different template.
  • post.hbs is the file used in the context of viewing a post.
  • index.hbs is the file used in the context of viewing the home page.
  • and so on

We've got full and extensive theme documentation which outlines every template file, context and helper that you can use.

If you want to chat with other people making Ghost themes to get any advice or help, there's also a themes section on our public Ghost forum.

Read more
Max

Upgrading my blog theme with the Vanilla CSS Framework

Recently I have spent more and more time thinking about the usability of my website and the cleanliness of its code.

This has brought me to another iteration in its design process.

Of course also related to my employment at Canonical I have decided to give our Vanilla Framework a try.

The first thing I had to update is my gulp task to include node_modules during the build.

//  CSS
gulp.task("css", function () {
  return gulp
    .src(`${source}/assets/css/styles.scss`)
    .pipe(sourcemaps.init())
    .pipe(sass({
      includePaths: ['node_modules']
    }).on('error', sass.logError))
    .pipe(sourcemaps.write("."))
    .pipe(gulp.dest("assets/built/"));
});

Otherwise it wouldnt pick up on the imports. This is also described on the framework page.

Then I have included the basic settings from Vanilla in my styles file with

@import "vanilla-framework/scss/settings";
@import "vanilla-framework/scss/base";
@include vf-base;

The @include is necessary because vanilla provides mixins via the imported files. This way I can control exactly what I want on my blog, thus reducing bloat and data usage.

Just by doing this, the website, given its semantic HTML already looks pretty nice:

Upgrading my blog theme with the Vanilla CSS Framework

One thing that I would still like to highlight though is the main content vs the head of the webpage. After browsing through the patterns at https://docs.vanillaframework.io/en/ for a bit I decided that I am only going to need the `p-card` pattern to achieve this.

After including this with @import "vanilla-framework/scss/patterns_card"; and using the needed mixins for the default card, the highlighted one and the content inside the card with

@include vf-p-card-default;
@include vf-p-card-highlighted;
@include vf-p-card-specific-content;

I just had to add classes to my articles on the index page and the content on the post page to achieve the following:

One thing that still bothers me here is the focus of the content on the left side. So I went ahead and adjusted some of the margins to the sides and on the header.

An additional thing I added (or actually removed) afterwards is the Font. If you set the $font-base-family variable before including the Vanilla base mixin, it will replace the default Ubuntu Font. As my goal is a very data saving, content focused website, I have replace it with serif, sans-serif. This will use the Fonts that are installed on the system in that order.

So, if for any reason, there is no Font found that has [serifs](https://en.wikipedia.org/wiki/Serif), a Font without serifs will be used instead.

Resume

In a few hours I was able to reach a design that is both clear and allows the user to focus on the content, which is written text. The Vanilla Framework, and the way it is set up, made it possible for me to focus on the things that are important to me and disregard the rest.

This way I can leverage all the design decisions around readability etc. that the Vanilla team has put into the framework and still have a bloat-free, brutalisitc website design to my liking, that represents me on the Internet.

This is a great speed improvement and asset for someone like me who appreciates great design, but spends a lot of time in the backend.

Read more
Max

Upgrading my blog theme with the Vanilla CSS Framework

Recently I have spent more and more time thinking about the usability of my website and the cleanliness of its code.

This has brought me to another iteration in its design process.

Of course also related to my employment at Canonical I have decided to give our Vanilla Framework a try.

The first thing I had to update is my gulp task to include node_modules during the build.

//  CSS
gulp.task("css", function () {
  return gulp
    .src(`${source}/assets/css/styles.scss`)
    .pipe(sourcemaps.init())
    .pipe(sass({
      includePaths: ['node_modules']
    }).on('error', sass.logError))
    .pipe(sourcemaps.write("."))
    .pipe(gulp.dest("assets/built/"));
});

Otherwise it wouldnt pick up on the imports. This is also described on the framework page.

Then I have included the basic settings from Vanilla in my styles file with

@import "vanilla-framework/scss/settings";
@import "vanilla-framework/scss/base";
@include vf-base;

The @include is necessary because vanilla provides mixins via the imported files. This way I can control exactly what I want on my blog, thus reducing bloat and data usage.

Just by doing this, the website, given its semantic HTML already looks pretty nice:

Upgrading my blog theme with the Vanilla CSS Framework

One thing that I would still like to highlight though is the main content vs the head of the webpage. After browsing through the patterns at https://docs.vanillaframework.io/en/ for a bit I decided that I am only going to need the `p-card` pattern to achieve this.

After including this with @import "vanilla-framework/scss/patterns_card"; and using the needed mixins for the default card, the highlighted one and the content inside the card with

@include vf-p-card-default;
@include vf-p-card-highlighted;
@include vf-p-card-specific-content;

I just had to add classes to my articles on the index page and the content on the post page to achieve the following:

One thing that still bothers me here is the focus of the content on the left side. So I went ahead and adjusted some of the margins to the sides and on the header.

An additional thing I added (or actually removed) afterwards is the Font. If you set the $font-base-family variable before including the Vanilla base mixin, it will replace the default Ubuntu Font. As my goal is a very data saving, content focused website, I have replace it with serif, sans-serif. This will use the Fonts that are installed on the system in that order.

So, if for any reason, there is no Font found that has [serifs](https://en.wikipedia.org/wiki/Serif), a Font without serifs will be used instead.

Resume

In a few hours I was able to reach a design that is both clear and allows the user to focus on the content, which is written text. The Vanilla Framework, and the way it is set up, made it possible for me to focus on the things that are important to me and disregard the rest.

This way I can leverage all the design decisions around readability etc. that the Vanilla team has put into the framework and still have a bloat-free, brutalisitc website design to my liking, that represents me on the Internet.

This is a great speed improvement and asset for someone like me who appreciates great design, but spends a lot of time in the backend.

Read more
Max

Impulse

Bug hunting on iOS 6.3.1

Me and my SO recently got an old iPad2, which we were planning to use for everyday usage around the house. Meaning, having it ready to research a recipe, get information about a topic or researching things to do together.

Unfortunately though, the iOS version that it came preinstalled with was 9.3.5. On this later version of iOS there is quite a problem with the responsiveness though.
Probably due to the big alterations on the design side the iPad feels slow and has noticeable lags when switching between applications, and even when using the web browser. The argument for this was, that it never seemed that slow before, and it should not be the hardware that wore down.
So I decided to try and downgrade the iPad and compare a much older version, namely 6.3.1, to the 9.3.5 that was installed.

After doing so successfully via ITunes and an old [IPSW](https://en.wikipedia.org/wiki/IPSW) file it was clear that the assumption was correct. Being back on the old version improved the performance significantly.
This only goes as far as the web though. When visiting certain websites errors started bringing the user satisfaction back down again.

Here are a a few screenshots of some broken styling:

And some websites were still a pleasure to use, here are the BBC and RA as examples:

Reflection

This made me think to go onto my own blog and start breaking it.
Given all the modern tech that I use for this theme, I would not be surprised to find some thing not working correctly. Mostly due to the CSS Grid specification I am using for the styling.

And indeed it did not take long:
First of all the header is broken, with the navigation links appearing in a horizontal manner, instead of a vertical one.
Additionally after some scrolling the header starts covering most of the content.

Bug hunting on iOS 6.3.1
Broken Header
Bug hunting on iOS 6.3.1
Header covering content

My speculation for the first bug being the CSS Grid, while the latter one could be related to the vh and vw units I am using.

Action

Since I am now in possession of a great testing device I set out to fix this.
Setting up my development environment by changing the the address of my development server to 0.0.0.0:8000, thus exposing it my local network, and accessing it on my iPad via IP.OF.MY.LAPTOP:8000, I have a quick feedback loop to test out my ideas and refactorings.

After looking at the stylings for the header the position: fixed; top: 0; turned out to be the problem.
Since this was a problem that was not solved elegantly initially I decided to throw it out completely and instead reiterate on the website design.

Since design in general is an iterative process I will take this as a major stepping stone in finding the right one for my blog.
For now the user (so you) will have to scroll back up to the top of the page in order to navigate. Given all the advanced technology that is on this site though, and its relative simplicity I will start from scratch for the layouting. This time using old methods and then adding modern techniques on top via cascading rules.

Resume

This rather random chain on events has again taught me, and hopefully some of you that are reading this, a lesson. While I always speak up for accessibility and its importantance in our work, I wanted to write the theme for this blog, using all the latest technologies, making my developer life comfortable and the code elegant.
This hypocrisy was made obvious right now, and I will have to backtrack and rework some parts. It also comes to show that some problems stem from architectural decisions rather than a specific bug. My assumptions about what could be breaking were not too far off, but as it turns out the problem has more depth.
While the CSS is easy to refactor and well decoupled, this could have become a nightmare if the project was bigger, since I have already accumulated tech-debt and would just keep on growing it.
But as always, a mistake made now is a mistake avoided in the future.

Cover photo by Tim Goedhart on Unsplash

Read more
Max

Impulse

Bug hunting on iOS 6.3.1

Me and my SO recently got an old iPad2, which we were planning to use for everyday usage around the house. Meaning, having it ready to research a recipe, get information about a topic or researching things to do together.

Unfortunately though, the iOS version that it came preinstalled with was 9.3.5. On this later version of iOS there is quite a problem with the responsiveness though.
Probably due to the big alterations on the design side the iPad feels slow and has noticeable lags when switching between applications, and even when using the web browser. The argument for this was, that it never seemed that slow before, and it should not be the hardware that wore down.
So I decided to try and downgrade the iPad and compare a much older version, namely 6.3.1, to the 9.3.5 that was installed.

After doing so successfully via ITunes and an old [IPSW](https://en.wikipedia.org/wiki/IPSW) file it was clear that the assumption was correct. Being back on the old version improved the performance significantly.
This only goes as far as the web though. When visiting certain websites errors started bringing the user satisfaction back down again.

Here are a a few screenshots of some broken styling:

And some websites were still a pleasure to use, here are the BBC and RA as examples:

Reflection

This made me think to go onto my own blog and start breaking it.
Given all the modern tech that I use for this theme, I would not be surprised to find some thing not working correctly. Mostly due to the CSS Grid specification I am using for the styling.

And indeed it did not take long:
First of all the header is broken, with the navigation links appearing in a horizontal manner, instead of a vertical one.
Additionally after some scrolling the header starts covering most of the content.

Bug hunting on iOS 6.3.1
Broken Header
Bug hunting on iOS 6.3.1
Header covering content

My speculation for the first bug being the CSS Grid, while the latter one could be related to the vh and vw units I am using.

Action

Since I am now in possession of a great testing device I set out to fix this.
Setting up my development environment by changing the the address of my development server to 0.0.0.0:8000, thus exposing it my local network, and accessing it on my iPad via IP.OF.MY.LAPTOP:8000, I have a quick feedback loop to test out my ideas and refactorings.

After looking at the stylings for the header the position: fixed; top: 0; turned out to be the problem.
Since this was a problem that was not solved elegantly initially I decided to throw it out completely and instead reiterate on the website design.

Since design in general is an iterative process I will take this as a major stepping stone in finding the right one for my blog.
For now the user (so you) will have to scroll back up to the top of the page in order to navigate. Given all the advanced technology that is on this site though, and its relative simplicity I will start from scratch for the layouting. This time using old methods and then adding modern techniques on top via cascading rules.

Resume

This rather random chain on events has again taught me, and hopefully some of you that are reading this, a lesson. While I always speak up for accessibility and its importantance in our work, I wanted to write the theme for this blog, using all the latest technologies, making my developer life comfortable and the code elegant.
This hypocrisy was made obvious right now, and I will have to backtrack and rework some parts. It also comes to show that some problems stem from architectural decisions rather than a specific bug. My assumptions about what could be breaking were not too far off, but as it turns out the problem has more depth.
While the CSS is easy to refactor and well decoupled, this could have become a nightmare if the project was bigger, since I have already accumulated tech-debt and would just keep on growing it.
But as always, a mistake made now is a mistake avoided in the future.

Cover photo by Tim Goedhart on Unsplash

Read more
Max

Adding Comments

The landscape for software that helps in adding comments to a website is does not shine so bright. A few big closed source companies, such as Disqus or Facebook offer an API to integrate comments. The problem with these services is that they own all the data and we have no Idea what we expose the website visitor to.

While Wordpress does come with comments baked into the platform, unfortunately Ghost and others do not. This article has a good overview of the problem in Ghost and I would identify myself as the power user, that wants a good product and respects users privacy.
That which is why I kept looking for solutions before seriously considering moving away from Ghost.

Looking at the comments (This is why we do need them) from the above article is where I have found the solution that works for me => Commento.
Available on Gitlab and with pretty good setup documentation, this commenting service allows functionalities similar to Disqus while giving all the benefits of hosting the software on your own server. Of course you can also let the company host this for you, at a really reasonable price, while still being able to see the Codebase it is running on.

If you do care about your users and would like to provide the people that visit your website with privacy and no third party ADs than you should give Commento a try.

Btw, I have no connection to them whatsoever. I just really like their product and would love for others to discover it!

Updated privacy policy on this Blog

Since the commenting functionality needs to store data I have updated the Privacy Policy.

Read more
Max

Adding Comments

The landscape for software that helps in adding comments to a website is does not shine so bright. A few big closed source companies, such as Disqus or Facebook offer an API to integrate comments. The problem with these services is that they own all the data and we have no Idea what we expose the website visitor to.

While Wordpress does come with comments baked into the platform, unfortunately Ghost and others do not. This article has a good overview of the problem in Ghost and I would identify myself as the power user, that wants a good product and respects users privacy.
That which is why I kept looking for solutions before seriously considering moving away from Ghost.

Looking at the comments (This is why we do need them) from the above article is where I have found the solution that works for me => Commento.
Available on Gitlab and with pretty good setup documentation, this commenting service allows functionalities similar to Disqus while giving all the benefits of hosting the software on your own server. Of course you can also let the company host this for you, at a really reasonable price, while still being able to see the Codebase it is running on.

If you do care about your users and would like to provide the people that visit your website with privacy and no third party ADs than you should give Commento a try.

Btw, I have no connection to them whatsoever. I just really like their product and would love for others to discover it!

Updated privacy policy on this Blog

Since the commenting functionality needs to store data I have updated the Privacy Policy.

Read more
Max

Theme update

By chance I stumbled across an old device with an outdated browser and decided to go with an entirely new approach for the theme of this blog.
The motivation was support for this device, which led to reanalyzing the whole concept of only using modern techniques. An article on this will follow in the next weeks.

The new design and Codebase is much more functional and simple, focusing on content and support for the most devices possible.
While the series on creating this theme is still an interesting read into the things I tried, it is now outdated and I would not recommend following it for the actual styling.
If you are interested in setting up the build system for a theme than it is still relevant.

If you find any bugs, please contact me and I will try to get them fixed ASAP.

Read more
Max

Theme update

By chance I stumbled across an old device with an outdated browser and decided to go with an entirely new approach for the theme of this blog.
The motivation was support for this device, which led to reanalyzing the whole concept of only using modern techniques. An article on this will follow in the next weeks.

The new design and Codebase is much more functional and simple, focusing on content and support for the most devices possible.
While the series on creating this theme is still an interesting read into the things I tried, it is now outdated and I would not recommend following it for the actual styling.
If you are interested in setting up the build system for a theme than it is still relevant.

If you find any bugs, please contact me and I will try to get them fixed ASAP.

Read more
Max

Please Note that this theme is not in use anymore. See this post for more info.

Ghost theme - Part 4: Finishing the Sidebar

On mobile Layouts < 360px width the Sidebar still overflows vertically. Lets fix it and implement the features decided on in Part 3.

First a small screenshot of the problem:

Ghost theme - Part 4: Finishing the Sidebar

As you can see the Sidebar flows into the other content.
To fix this lets remove some margins and change the nav to list its items horizontally.

All done with this bit of CSS in the styles.css file since we are changing the default layout.

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-wrap: wrap;
  max-height: var(--sidebar-height);

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}
@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

This has actually become quite a lot to keep in the styles.css file.
Lets extract it into components/sidebar.css and import it.

All should look good now. Except when the content doesnt wrap!
Here is a screenshot of what I mean:

Ghost theme - Part 4: Finishing the Sidebar

Everything is in a row. The fix is quite easy with flexbox. Lets just set the flex-direction to column. That should fix it. Right?

Almost. The flex-wrap: wrap from earlier now messes things up. So lets just get rid of it on the sidebar. It worked great when the direction was row but now it is wrapping at the vertical ends, which is not what we want.

Now the sidebar looks pretty nice and our layout is almost done!

The features

Lets go on to the features:

  1. when scrolling the Title disappears
  2. when scrolling the Description disappears
  3. Only the Navigation is shown and the sidebar should shrink to its size but stay at the top

With position: sticky; the last one is pretty easy to do these days.
But to hide the other two we would need to restructure the HTML of our Sidebar since the position is relative to its parent. Which means it would still go out of the screen together with it.

So the last resort here is JavaScript together with position: fixed;. In general we should not use JavaScript for styling, but for this weird case lets make an exception and add a new class to the sidebar when it scrolls out of view.
Using that class we can then use the correct styles to achieve our goal and maybe add some nice CSS Animation.

Lets code

To use JavaScript create the file assets/js/helpers/styling.js.
The gotede build tools will take care of doing all the hard work of getting it onto the page, such as using babel and concatenating the JavaScript files you create.
Just take note that imports wont work. Since this is a Theme for purely Server-side rendered pages, that is totally fine. The JavaScript should be kept to a minimum here.

It should contain the following code, which will attach a new class to the Sidebar when we scroll down and then remove this class again once we scroll to the top:

function initScrollingListener() {
  const sidebar = document.getElementsByClassName("sidebar")[0];

  function addScrollClass() {
    if (window.pageYOffset >= 0) {
      sidebar.classList.add("scrolled");
      window.removeEventListener("scroll", addScrollClass);
      window.addEventListener("scroll", removeScrollClass);
    }
  }

  function removeScrollClass() {
    if (window.pageYOffset == 0) {
      sidebar.classList.remove("scrolled");
      window.removeEventListener("scroll", removeScrollClass);
      window.addEventListener("scroll", addScrollClass);
    }
  }

  if (window.pageYOffset == 0) {
    window.addEventListener("scroll", addScrollClass);
  } else {
    addScrollClass();
  }
}

initScrollingListener();

You should have some basic understanding of programming to really understand what is happening here but here is a small breakdown:

1.  If pageYOffset is 0 we are at the top, so we add a Listener that will call our addScrollClass function once we scroll. Otherwise we are not at the top and can call that function directly.
2. addScrollClass adds the class to the Sidebar element when we are not at the top of the page and the removes the Listener for calling itself on scrolling. Then it adds the Listener that calls removeScrollClass when we scroll.
3. removeScrollClass does exactly the opposite of addScrollClass.

Here is a list of all the DOM functionalities that are used in this piece of code: window, document, getElementsByClassName, window.pageYOffset, Element.classList, addEventListener, removeEventListener

Maybe you notice that we will call one of these functions every time that we scroll. In this case it is a very small amount of code, but it still has to run on the CPU, which is why we should generally avoid these kind of hacks.

Using the new class

So lets go and implement the things that should happen when we scroll in CSS.

.sidebar {
    /* ... */
    position: fixed;
    /* ... */
    &.scrolled{
        height: calc(var(--sidebar-height) / 1.5);
        width: var(--sidebar-width);

        & .page-title {
           display: none; 
        }

        & .page-description {
           display: none; 
        }
      }

This is all we need for now.
It should be inside the .sidebar block and hides both the description and the title when the scrolled class is added.

While it is not an optimal solution, since we use JavaScript for styling, it works in this case and does not put much load on the CPU since we only add two Listeners to our Window.

An Animation

To add some spiciness to our Sidebar suddenly reducing in size, we can add a small Animation when this happens.
Lets just make a dummy one for now where the Background transitions through colors. We can fine-tune this later when we got to the real colors of the theme.

  &.scrolled {
    /*...*/
    animation: background-transition 3s;
    background-color: yellow;
    z-index: 1;
  }
  /*...*/
  
  @keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
  }

One more thing is left to do.
While we have a nice Sidebar for a mobile layout these rules will break the Desktop design. Since we would have to overwrite all these rules in a MediaQuery lets make it easy on us and instead wrap these inside one themselves to keep the default on the other layouts.
To do this put all the CSS we just created for the scrolled class inside @media screen and (max-width: 960px) and dont forget to wrap them inside the .sidebar class as well.

The final sidebar.css should look like this:

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-direction: column;
  max-height: var(--sidebar-height);

  position: fixed;
  top: 0;

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}

@media screen and (max-width: 960px) {
    .sidebar {
        &.scrolled {
            height: calc(var(--sidebar-height) / 1.5);
            width: var(--sidebar-width);
            animation: background-transition 3s;
            background-color: yellow;
            z-index: 1;

        & .page-title {
            display: none; 
        }

        & .page-description {
            display: none; 
        }
        }
    }
}

@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

@keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
}

The end

Here is a GIF of the new behaviour:

Ghost theme - Part 4: Finishing the Sidebar

Caught that Image that overflows our page? A Bug, how nice. I will fix it by next time. If you somehow followed along and want to do this as well, check out the class of such and element and try to find a setting that restricts its width ;)

Otherwise it looks like we are done here from a Basic Layouting point of view.
Of course there is still much needed on the visual front, which is what we will start in Part 6, where it is going to be all about Typography and choosing a Font.

Cover: https://unsplash.com/photos/SXihyA4oEJs

Read more
Max

Please Note that this theme is not in use anymore. See this post for more info.

Ghost theme - Part 4: Finishing the Sidebar

On mobile Layouts < 360px width the Sidebar still overflows vertically. Lets fix it and implement the features decided on in Part 3.

First a small screenshot of the problem:

Ghost theme - Part 4: Finishing the Sidebar

As you can see the Sidebar flows into the other content.
To fix this lets remove some margins and change the nav to list its items horizontally.

All done with this bit of CSS in the styles.css file since we are changing the default layout.

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-wrap: wrap;
  max-height: var(--sidebar-height);

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}
@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

This has actually become quite a lot to keep in the styles.css file.
Lets extract it into components/sidebar.css and import it.

All should look good now. Except when the content doesnt wrap!
Here is a screenshot of what I mean:

Ghost theme - Part 4: Finishing the Sidebar

Everything is in a row. The fix is quite easy with flexbox. Lets just set the flex-direction to column. That should fix it. Right?

Almost. The flex-wrap: wrap from earlier now messes things up. So lets just get rid of it on the sidebar. It worked great when the direction was row but now it is wrapping at the vertical ends, which is not what we want.

Now the sidebar looks pretty nice and our layout is almost done!

The features

Lets go on to the features:

  1. when scrolling the Title disappears
  2. when scrolling the Description disappears
  3. Only the Navigation is shown and the sidebar should shrink to its size but stay at the top

With position: sticky; the last one is pretty easy to do these days.
But to hide the other two we would need to restructure the HTML of our Sidebar since the position is relative to its parent. Which means it would still go out of the screen together with it.

So the last resort here is JavaScript together with position: fixed;. In general we should not use JavaScript for styling, but for this weird case lets make an exception and add a new class to the sidebar when it scrolls out of view.
Using that class we can then use the correct styles to achieve our goal and maybe add some nice CSS Animation.

Lets code

To use JavaScript create the file assets/js/helpers/styling.js.
The gotede build tools will take care of doing all the hard work of getting it onto the page, such as using babel and concatenating the JavaScript files you create.
Just take note that imports wont work. Since this is a Theme for purely Server-side rendered pages, that is totally fine. The JavaScript should be kept to a minimum here.

It should contain the following code, which will attach a new class to the Sidebar when we scroll down and then remove this class again once we scroll to the top:

function initScrollingListener() {
  const sidebar = document.getElementsByClassName("sidebar")[0];

  function addScrollClass() {
    if (window.pageYOffset >= 0) {
      sidebar.classList.add("scrolled");
      window.removeEventListener("scroll", addScrollClass);
      window.addEventListener("scroll", removeScrollClass);
    }
  }

  function removeScrollClass() {
    if (window.pageYOffset == 0) {
      sidebar.classList.remove("scrolled");
      window.removeEventListener("scroll", removeScrollClass);
      window.addEventListener("scroll", addScrollClass);
    }
  }

  if (window.pageYOffset == 0) {
    window.addEventListener("scroll", addScrollClass);
  } else {
    addScrollClass();
  }
}

initScrollingListener();

You should have some basic understanding of programming to really understand what is happening here but here is a small breakdown:

1.  If pageYOffset is 0 we are at the top, so we add a Listener that will call our addScrollClass function once we scroll. Otherwise we are not at the top and can call that function directly.
2. addScrollClass adds the class to the Sidebar element when we are not at the top of the page and the removes the Listener for calling itself on scrolling. Then it adds the Listener that calls removeScrollClass when we scroll.
3. removeScrollClass does exactly the opposite of addScrollClass.

Here is a list of all the DOM functionalities that are used in this piece of code: window, document, getElementsByClassName, window.pageYOffset, Element.classList, addEventListener, removeEventListener

Maybe you notice that we will call one of these functions every time that we scroll. In this case it is a very small amount of code, but it still has to run on the CPU, which is why we should generally avoid these kind of hacks.

Using the new class

So lets go and implement the things that should happen when we scroll in CSS.

.sidebar {
    /* ... */
    position: fixed;
    /* ... */
    &.scrolled{
        height: calc(var(--sidebar-height) / 1.5);
        width: var(--sidebar-width);

        & .page-title {
           display: none; 
        }

        & .page-description {
           display: none; 
        }
      }

This is all we need for now.
It should be inside the .sidebar block and hides both the description and the title when the scrolled class is added.

While it is not an optimal solution, since we use JavaScript for styling, it works in this case and does not put much load on the CPU since we only add two Listeners to our Window.

An Animation

To add some spiciness to our Sidebar suddenly reducing in size, we can add a small Animation when this happens.
Lets just make a dummy one for now where the Background transitions through colors. We can fine-tune this later when we got to the real colors of the theme.

  &.scrolled {
    /*...*/
    animation: background-transition 3s;
    background-color: yellow;
    z-index: 1;
  }
  /*...*/
  
  @keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
  }

One more thing is left to do.
While we have a nice Sidebar for a mobile layout these rules will break the Desktop design. Since we would have to overwrite all these rules in a MediaQuery lets make it easy on us and instead wrap these inside one themselves to keep the default on the other layouts.
To do this put all the CSS we just created for the scrolled class inside @media screen and (max-width: 960px) and dont forget to wrap them inside the .sidebar class as well.

The final sidebar.css should look like this:

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-direction: column;
  max-height: var(--sidebar-height);

  position: fixed;
  top: 0;

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}

@media screen and (max-width: 960px) {
    .sidebar {
        &.scrolled {
            height: calc(var(--sidebar-height) / 1.5);
            width: var(--sidebar-width);
            animation: background-transition 3s;
            background-color: yellow;
            z-index: 1;

        & .page-title {
            display: none; 
        }

        & .page-description {
            display: none; 
        }
        }
    }
}

@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

@keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
}

The end

Here is a GIF of the new behaviour:

Ghost theme - Part 4: Finishing the Sidebar

Caught that Image that overflows our page? A Bug, how nice. I will fix it by next time. If you somehow followed along and want to do this as well, check out the class of such and element and try to find a setting that restricts its width ;)

Otherwise it looks like we are done here from a Basic Layouting point of view.
Of course there is still much needed on the visual front, which is what we will start in Part 6, where it is going to be all about Typography and choosing a Font.

Cover: https://unsplash.com/photos/SXihyA4oEJs

Read more
Max

CSS Modules: the correct code split

There is a lot of talk going on in the JavaScript Frontend Community about using CSSinJS. One example for this are styled-components.
A use of them is explained over at https://www.toptal.com/javascript/styled-components-library .
In this Article I want to name an alternative and talk about some benefits.

What are CSS modules?

According to the css-modules github repo:

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

For use in React or Vue f.e. this would mean:
All of the styles inside of a CSS Module will not affect anything else than the Component on which they are imported in.

Using CSS modules

Lets try this out with a a bit of React.
First we write a Box Component like this:

import React from 'react';
import styles from './Box.css';

const Box = () => <div />;

export default Box;

And apply some styles to it:

import React from 'react';
import styles from './Box.css';

const Box = () => <div className={styles.box} />;

export default Box;

Notice how we just import some CSS file into our JavaScript. This is where the magic happens.
In this case the CSS modularization happens through the webpack css-loader.

This is the Box.css file that gets imported.

.box {
    width: 400px;
    height: 300px;
    border-bottom: solid brown 4px;
    border-right: solid brown 4px;
    border-left: solid brown 4px;
}

we can check out the browser to see what the result looks like.

CSS Modules: the correct code split

Notice the class name? This is how CSS Modules achieve the local scoping. The imported class will be added on the correct Components and in the process their names will be changed and a unique hash added.
This will prevent any collisions with rules of other components.

No collision

Lets try this out in the browser.
Here are 2 components that use the same class name in their CSS:

Banana Component

import React from 'react';
import styles from './Banana.css';

const Banana = () => (
  <div className={styles.container}>
    <p className={styles.text}>Banana</p>
  </div>
);

export default Banana;
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    border: solid 1px yellow;
}

.text {
    color: yellow;
}

Apple Component

import React from 'react';
import styles from './Apple.css';

const Apple = () => (
  <div className={styles.container}>
    <p className={styles.text}>Apple</p>
  </div>
);

export default Apple;
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    border: solid 1px green;
}

.text {
    color: green;
}

Both of the components use the same classes inside of them. If we would write normal CSS this should lead to collisions, meaning that one set of rules would overwrite the other.
But checking the website again we can see that everything is normal.
Take note of the hashed class names again.

CSS Modules: the correct code split

All collisions are avoided by making the class names unique. Similar to techniques such as BEM, just in an automated way.

Conclusion

This was a very brief introduction to CSS Modules but should give a good understanding of what they do and how they work.
Some of the features are similar to techniques such as styled-components, but by being just CSS files we can benefit from some advantages.
Lets dive into the Pros and Cons to wrap it up:

Pros

  • Designers do not need to learn new ways of writing CSS in JS. Instead they can easily make changes to the correct CSS files. This way of styling, combined with mental models such a atomic design, can increase communication between Developers and Designers and development speed.
  • Separation of Concerns: We hear this all the time in Web Development and it was regarded as best practice for a long time. Given JSXs popularity one might argue that this is not true anymore, but we should still aim for it.
  • Tools and Preprocessors such as PostCss can be easily integrated to allow CSS Imports, Custom Properties etc.

Cons

  • The build task might be complicated to set up at first
  • Global styles have to be wrapped in a special :global block. Where would such a block belong?
  • The power of JavaScript is not available inside the CSS files

Cover Image is from https://unsplash.com/photos/8EzNkvLQosk

Read more
Max

CSS Modules: the correct code split

There is a lot of talk going on in the JavaScript Frontend Community about using CSSinJS. One example for this are styled-components.
A use of them is explained over at https://www.toptal.com/javascript/styled-components-library .
In this Article I want to name an alternative and talk about some benefits.

What are CSS modules?

According to the css-modules github repo:

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

For use in React or Vue f.e. this would mean:
All of the styles inside of a CSS Module will not affect anything else than the Component on which they are imported in.

Using CSS modules

Lets try this out with a a bit of React.
First we write a Box Component like this:

import React from 'react';
import styles from './Box.css';

const Box = () => <div />;

export default Box;

And apply some styles to it:

import React from 'react';
import styles from './Box.css';

const Box = () => <div className={styles.box} />;

export default Box;

Notice how we just import some CSS file into our JavaScript. This is where the magic happens.
In this case the CSS modularization happens through the webpack css-loader.

This is the Box.css file that gets imported.

.box {
    width: 400px;
    height: 300px;
    border-bottom: solid brown 4px;
    border-right: solid brown 4px;
    border-left: solid brown 4px;
}

we can check out the browser to see what the result looks like.

CSS Modules: the correct code split

Notice the class name? This is how CSS Modules achieve the local scoping. The imported class will be added on the correct Components and in the process their names will be changed and a unique hash added.
This will prevent any collisions with rules of other components.

No collision

Lets try this out in the browser.
Here are 2 components that use the same class name in their CSS:

Banana Component

import React from 'react';
import styles from './Banana.css';

const Banana = () => (
  <div className={styles.container}>
    <p className={styles.text}>Banana</p>
  </div>
);

export default Banana;
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    border: solid 1px yellow;
}

.text {
    color: yellow;
}

Apple Component

import React from 'react';
import styles from './Apple.css';

const Apple = () => (
  <div className={styles.container}>
    <p className={styles.text}>Apple</p>
  </div>
);

export default Apple;
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    border: solid 1px green;
}

.text {
    color: green;
}

Both of the components use the same classes inside of them. If we would write normal CSS this should lead to collisions, meaning that one set of rules would overwrite the other.
But checking the website again we can see that everything is normal.
Take note of the hashed class names again.

CSS Modules: the correct code split

All collisions are avoided by making the class names unique. Similar to techniques such as BEM, just in an automated way.

Conclusion

This was a very brief introduction to CSS Modules but should give a good understanding of what they do and how they work.
Some of the features are similar to techniques such as styled-components, but by being just CSS files we can benefit from some advantages.
Lets dive into the Pros and Cons to wrap it up:

Pros

  • Designers do not need to learn new ways of writing CSS in JS. Instead they can easily make changes to the correct CSS files. This way of styling, combined with mental models such a atomic design, can increase communication between Developers and Designers and development speed.
  • Separation of Concerns: We hear this all the time in Web Development and it was regarded as best practice for a long time. Given JSXs popularity one might argue that this is not true anymore, but we should still aim for it.
  • Tools and Preprocessors such as PostCss can be easily integrated to allow CSS Imports, Custom Properties etc.

Cons

  • The build task might be complicated to set up at first
  • Global styles have to be wrapped in a special :global block. Where would such a block belong?
  • The power of JavaScript is not available inside the CSS files

Cover Image is from https://unsplash.com/photos/8EzNkvLQosk

Read more
Max

Please Note that this theme is not in use anymore. See this post for more info.

Ghost theme - Part 3: Mobile Layout

Here is the third part of this series where we will dive into creating the proper layout for the posts on the front page and figure out a way to display our layout on mobilephones.

Just in case lets bring up the inital sketch again

Ghost theme - Part 3: Mobile Layout

But before we can jump in and take care of the posts we have a small problem.
Using the mobile page preview of the browser we can easily spot it.

Ghost theme - Part 3: Mobile Layout

The content inside the sidebar does not fit inside the container anymore. Unfortunately max-width does not help here, as the words are actually too long and the browser cant wrap the text to the next line.

So its time to come up with a new sketch:

Ghost theme - Part 3: Mobile Layout

You have probably encountered this technique before. Here is a break down:

  1. We will make the sidebar go to the top on mobile devices.
  2. The Title and subtitle will scroll out of the screen
  3. The navigation elements will stay on top

Implementation

Responsiveness

The first thing I am going to do is to convert the whole layout to this idea.
This 'old' design will be wrapped via media-queries that activate when a certain screen-size is reached.
This way of doing it is called mobile first.
Since this theme is in an early stage of development it is still easily possible to switch to this approach.

This is the updated CSS:

variables.css

:root {
  /* Container sizes */
  --main-width: 100vw;
  --main-height: 80vh;

  --sidebar-width: 100vw;
  --sidebar-height: 20vh;
}

@media screen and (min-width: 961px) {
  :root {
    /* Container sizes */
    --main-width: 80vw;
    --main-height: 100%;

    --sidebar-width: 20vw;
    --sidebar-height: 100vh;
  }
}

styles.css

@import "./helpers/variables.css";

/* Mobile */
body {
  margin: 0px;
}

.grid-container {
  display: grid;
  height: 100%;
  grid-template-columns: 1fr;
  grid-template-rows: var(--sidebar-height) var(--main-height);
  grid-gap: 0px;
  grid-template-areas: "Sidebar" "Content";
}

.main {
  grid-area: Content;
  max-width: var(--main-width);
}

.sidebar {
  grid-area: Sidebar;
}


/* Desktop */
@media screen and (min-width: 961px) {
  .grid-container {
    display: grid;
    height: 100%;
    grid-template-columns: var(--sidebar-width) var(--main-width);
    grid-template-rows: 1fr;
    grid-gap: 0px;
    grid-template-areas: "Sidebar Content";
  }
  .main {
    grid-area: Content;
    max-width: var(--main-width);
  }
}

Et voila, we have a responsive page.

Mobile:
Ghost theme - Part 3: Mobile Layout

Desktop:
Ghost theme - Part 3: Mobile Layout

The Sidebar

As you can see the content of the Sidebar seems to escape into regions it shouldnt.

To fix this lets first change the default.hbs and remove the <div class="sidebar> and attach that class directly to the <header>.

Back in the styles.css we can now add some rules.
I am going to go for a flexbox solution.

 .sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-wrap: wrap;
  max-height: var(--sidebar-height);
}
  1. Tell the component that its display type is flexbox.
  2. Make sure that the content will wrap when it reaches the bounds of this container
  3. Set a maximum height on the sidebar. To make sure it doesnt grow beyond what we would like to have in our design. This is espacially useful when the sidebar is actually at the side.

Lets leave the additional requirements of hiding the title and subtitle for the next post.

The posts

Lastly its time to implement the post layout.
On the mobile front everything looks good already. One post per row.

For the desktop we need to find a way to display 2 posts next to each other though.

First I will create a new file components/index.css that will hold all the styles for the Index page. All the styling until now was for the default.hbs.
Also do not forget to import this file in the styles.css via @import "./components/index.css";.

Here is the CSS that will accomplish the task:

.index__posts {
  display: flex;
  flex-wrap: wrap;

  & .post {
    width: var(--post-width);
  }
}

You can see that the .post class is nested inside the .index__posts class. This again is taken care of during the build step via postcss-preset-env.

The --post-width variable is inside the variables.css file and looks like this:

/* .... */
:root {
  --post-width: var(--main-width);
}

@media screen and (min-width: 961px) {
  :root {
    /* .... */
    --post-width: calc(var(--main-width) / 2);
  }

This works in the following way:

  1. When the Screen is 961px wide or bigger every post will have the width of half the --main-width.
  2. Posts are inside a flexbox container
  3. Since the flexbox container does not wrap by default and instead rezises our posts to fit inside of it, we tell it to wrap the content.
  4. 2 posts will be shown per 'row' :)

The one thing I have not mentioned until now is the CSS calc function, which allows us to dynamically calculate some values.

Wrap up

Phew, this post had quite a lot of content and advanced techniques. Nevertheless the theme is now in a good state with relatively little CSS and should be pretty maintainable and extendable.

The neat little trick with the custom-properties is something that I have added to postcss-custom-properties while writing this, so it might not yet be released. I will update this post once it is.

Here is a final image of the Desktop page, looks pretty good already :

Ghost theme - Part 3: Mobile Layout

Next post

Whats going to happen in part 4?
Lets fix the Sidebar for mobile layouts. Afterwards in part 5 the Basics should be done to start with Typography and Colours :)

Cover: https://unsplash.com/photos/-1_RZL8BGBM

Read more