Customizing the Design of this Site
January 22, 2018
Part of the series Developing a Custom Theme
The default Jekyll Site uses the minima
theme. This theme is great for getting
started with the basic content of the site, but I prefer to know what’s going
on behind the curtain. What exactly does Jekyll use to convert my markdown files
into a complete static site? To learn about this, I made a custom theme for this
site. The theme’s source code is posted on GitHub.
How do Jekyll Themes Work?
Jekyll uses Ruby Gems to store themes. A theme is a collection of named HTML
files with Liquid markup to insert the site content. The behavior
of each HTML/Liquid template can be controlled using front matter in each site
file. This front matter is also the way that Jekyll knows which template file to
use - the layout
keyword indicates which HTML file to render. Overall, the
process looks like this:
- The site file specifies a layout in its front matter.
- Jekyll parses the site file and stores the file in named variables.
- Jekyll loads the appropriate HTML template from the theme. The template file gets loaded with the variables parsed in step 2.
- The fully rendered HTML file is saved in a build directory, which defaults
to
_site
.
This is the main process that Jekyll runs through to generate a static website from a series of HTML files. All of the plugins available for Jekyll will alter how an individual step in the process is completed; however, the overall process is the same.
As an example, let’s go through step-by-step how this page is rendered. You can
view the source of this page here. Notice the layout’s front
matter specifies the post
layout; therefore, Jekyll will look for the file
post.html
to convert the source to HTML. You can view post.html
for this
site’s theme here.
The first thing to notice is that post.html
specifies the default
layout in
its own front matter. Just like markdown files can include front matter, theme
files can also contain front matter. The rendering engine is the same as well;
Jekyll will take the contents of post.html
and will place it in the content
variable of default.html
. With nested templates, it is possible to break down
complex page layouts into easy-to-read layout files.
The custom theme for this site uses several layout components to compose all of
the pages. Every page is based off of the default
layout, which includes a
header, body, and footer. There are also optional layouts that can be included
on a per-page basis by setting variables in a page or post’s front matter. The
hero banner on the front page and the category menu are examples of optional
layouts. These optional layouts are included with the liquid tag
{% include %}
.
This site currently has three different layouts: post
, page
, and front
.
This post is rendered using the post
layout, as specified previously. The
homepage uses the front
layout. The About page is an example of the page
layout. As I discover the need for more types of layouts, I will add them to
this site and detail how they work.
A final note about the site’s theme: the design of this site uses Bulma to manage layout and styling. While its not the most flexible CSS framework available, it does provide plenty of customization and allows me to focus on the content. The documentation is extensive and provides detailed examples of how to structure the site’s HTML. Take a look at Bulma’s documentation if you would like to understand the specifics of each layout.
The Layouts of this Theme
In order to fully explain how this theme was developed, I will explain how I
built the layouts included in this theme. Currently, my goal for this theme is
to obtain feature parity with the minima
theme. In future posts in this
series, I will explain how these basic themes changed to fit my needs.
default.html
Every theme starts with a base layout. This layout will contain the core
identity that all other layouts will tweak. In this theme, the base layout is
default.html
. Like all HTML documents, it starts with the base document
layout:
<!DOCTYPE html>
<html>
{% include head.html %}
<body>
</body>
</html>
I’ve separated the <head>
content into a separate template, head.html
. This
is to keep all of the information belonging to the head of the document in its
own place.
It’s also good to describe the language of the document. This is done via the
lang
attribute on the <html>
tag.
<html lang="{{ page.lang | default: site.lang | default: 'en' }}">
Use the page language first, falling back to the site language if the page doesn’t specify a language, and using English if neither are specified.
Many websites have a header and a footer. This site is no exception. However,
we’ll separate the header and the footer into separate templates like with the
header above. Also, we’ll use a <section>
tag for the main content.
<body>
{% include header.html %}
<section>
{{ content }}
</section>
{% include footer.html %}
</body>
Now it’s time to format the layout. I decided with this theme to use a single
column layout to focus on the content with an optional sidebar menu. To make the
menu optional, the template must specify menu: true
in its YAML Front Matter
to include the menu. The front
layout does this below. Anyways, here is what
the main content looks like, expanded upon the section tags in the previous
excerpt.
<section class="section">
<div class="container">
{% if layout.menu %}
<div class="columns">
<div class="column is-narrow">
{% include components/menu.html %}
</div>
<div class="column">
{% endif %}
<main class="page-content" aria-label="Content">
<div class="wrapper">
{{ content }}
</div>
</main>
{% if layout.menu %}
</div>
</div>
{% endif %}
</div>
</section>
All of the classes here except for the main tag are part of Bulma. Look through the documentation there to see how each class contributes to the overall layout.
One more snippet makes the default layout complete: an optional hero banner.
This banner adds a splash of color and can be seen on the front page of the
website. Like the menu, it too must be specified with hero: true
in order to
be activated. Since it should appear at the top of the page, it appears between
the header and the <section>
tag. This snippet sets up the hero banner:
{% if layout.hero %}
{% include components/hero.html %}
{% endif %}
Altogether, this code makes for a customizable default template that suits the needs of this site. The full code for this template is available here.
post.html
The post
template defines the main content for posts (including this one). As
specified in the introduction, this template inherits the default
template
via the front matter.
---
layout: default
---
<article class="post">
</article>
This will cause all of the contents of the post
layout to be inserted into the
{{ content }}
tag of the default
layout. We can inherit
the head, header, and footer from the default template. Since the post content
is the primary focus, this template will not include the optional hero or menu
template.
There are two parts to the post
layout: the header and the content. The header
is the most complex. It contains the post title, date, and tags. There’s a bit
of Bulma styling here to make it render nicely as well.
<section class="post-header">
<div class="level">
<div class="level-left">
<header class="post-title">
<p class="title is-3 has-text-primary">
{{ page.title | escape }}
</p>
<p class="subtitle is-5">
{{ page.date | date: "%B %-d, %Y" }}
</p>
</header>
</div>
<div class="level-right">
<div class="tags">
{% for tag in page.tags %}
<span class="tag is-light">{{ tag }}</span>
{% endfor %}
</div>
</div>
</div>
</section>
<hr>
The header is a combination of Bulma layout and Jekyll specific Liquid tags, and so is the content.
<section class="post-content">
<div class="content">
{{ content }}
</div>
</section>
The post
layout is heavily focused on text. In a future iteration I hope to
add better support for images and other media types as well. You can see the
full code of the post template here.
page.html
The page
template is identical to the post
layout, except it can specify a
long-title to use instead of the normal title. This is due to the fact that any
page with a title by default gets added to the navigation bar in the header of
the site. Below is the content of the page
template:
<article class="page">
<div class="level">
<div class="level-left">
<header class="page-header">
<p class="title is-3 has-text-primary">
{{ page.long-title | default: page.title | escape }}
</p>
<p class="subtitle is-5">
{{ page.date | date: "%B %-d, %Y" }}
</p>
</header>
</div>
<div class="level-right">
<div class="tags">
{% for tag in page.tags %}
<span class="tag is-light">{{ tag }}</span>
{% endfor %}
</div>
</div>
</div>
<hr>
<div class="content">
{{ content }}
</div>
</article>
You can see the full code of the page template here.
front.html
The front
layout is designed to be the homepage of this website. It’s intent is to
provide a helpful summary of the site’s content and serve as an entry point to explore
the rest of the site. It’s layout is quite different from the page
and post
templates. To start, its front matter does include the optional hero and menu
components.
---
layout: default
hero: true
menu: true
---
Other than that, the front
layout is very simple. It defines a <section>
to
contain the main content, and then kicks off to include a series of the most recent
posts.
<section class="front">
{{ content }}
</section>
{% include components/recent-posts.html %}
The recent-posts
component lists the most recent posts in the site. In fact, its
layout is very similar to the header of the post
template. The noticeable difference
is the additional liquid logic: it performs additional checks to add a horizontal line
between posts, and it limits the tags displayed to three.
<section class="recent-posts">
{% for post in site.posts %}
<article class="media">
<div class="media-content">
<div class="content">
<div class="level">
<div class="level-left">
<div class="wrapper">
<p class="title is-4">
<a href="{{ post.url | relative_url }}">
{{ post.title | escape }}
</a>
</p>
<p class="subtitle is-6">
{{ post.date | date: "%b %-d, %Y" }}
</p>
</div>
</div>
<div class="level-right">
<div class="tags">
{% for category in post.categories %}
<span class="tag is-info">
{{ category | escape }}
</span>
{% endfor %}
{% for tag in post.tags %}
<span class="tag is-light">
{{ tag | escape }}
</span>
{% if forloop.index > 3 %}
{% break %}
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="post-excerpt">
{{ post.excerpt }}
</div>
</div>
</div>
</article>
{% if forloop.last == false %}
<hr>
{% endif %}
{% endfor %}
</section>
You can see the full code of the front template here.
Summary
These code snippets make up a complete Jekyll theme. I did not show the head
,
header
, and footer
includes that the default template uses. It is worthwhile to
have a look at those as well to gain a complete understanding of how the site fully
works. You can view the theme’s source on GitHub.
Over the course of these series of posts, I will introduce additional features to customize the theme further. All of the code in this theme is under the MIT License and can be customized to suit your needs.