How to build your own theme for Grav CMS

Grav is an open source flat-file CMS platform, built by the RocketTheme Team. While there are plenty of great CMS platforms available, they are all mostly database-driven, which can be overkill for smaller websites. Instead of a database, Grav uses folders and a basic file structure, it is focused on speed, simplicity, and flexibility.

After reading all of the documentation and spending some time trying Grav out, I’m definitely sold and will be using the platform. I hope you will too.

What We’ll Be Building

For the first part of this tutorial we’ll be building a one-page website, and in the second part, we’ll build a simple blog. I’ll assume you have a good understanding of HTML and CSS (or CSS preprocessors). We will not be getting into the styling of the themes but rather focus on Grav, and its functionality.

You can check out both of these themes on GitHub:

Installing Grav

Below are the very few requirements Grav needs in order for it to run:

  • A web server (Apache, Nginx, LiteSpeed, Lightly, IIS, etc.)
  • PHP 5.5.9 or higher

Download Grav Core with the Admin Panel Plugin and unzip the package in the webroot of your web server and you’re ready to roll.

Page Types

Grav comes with 3 types of pages out of the box:

Standard Page

These are typically single pages, such as blog posts, contact pages, error pages, etc. Grav assumes that any page is a standard page unless otherwise specified.

Listing Page

These are basically a standard page that has a reference to a collection of pages, for an example, a blog listing page. Configuration settings for these pages include: order, number of items and whether or not pagination is enabled.

Modular Page

Modular pages build a single page from it’s child pages, allowing us to build one-page layouts from smaller modular pages.

File Structure

Generally speaking, the only folder you’ll use is the /user folder.

The Content

The /user/pages folder is where all of the content pages live. Each page is placed in its own folder, and folder names should reflect the page’s name, and also be a valid slug.

You can order pages by naming the page folders with a preceding number: 01.home, 02.blog. Page folders then contain a markdown file and media for the page. The name of the markdown file will reference the name of the theme’s template to be used to render the content, for example: home.md would look for a template named home.html.twig.

The Theme

Themes can be found within the /user/themes folder. For a theme to function you’ll need:

  • blueprints.yaml – a file which contains information about the theme.
  • themename.php – a file which contains any logic your theme needs.
  • themename.yaml – a configuration file used by the plugin to set options the theme might use.
  • templates/ – a folder containing the Twig templates to render the pages.

You should also include and these are required if you plan to release a theme:

  • CHANGELOG.md – a file that follows the Grav Changelog Format to show changes.
  • LICENSE – a file containing the license to the theme.
  • README.md – a file with documentation for the theme.
  • screenshot.jpg – a 1009px x 1009px screenshot of the theme.
  • thumbnail.jpg – a 300px x 300px screenshot of the theme.

This is also where the css, sass, fonts, images, and js folders for the theme reside.

The Templates

Templates can be found in the /user/themes/themename/templates folder. These templates are Twig templates and will be used to render your pages.

The Blueprints

Blueprints are located in the /user/themes/themename/blueprints folder. The files within this folder are YAML files used to extend and modify the admin plugin with custom forms to make updating the website simpler.

Part 1: One Pager modular theme

onepager

Now that we have a basic understanding of how Grav works, let’s get started on our first Grav theme: a one page theme showcasing Grav’s awesomeness. Below is what our content and themes folder are going to look like:

Content File Structure

├── 01.home
│   ├── _download
│   │   └── download.md
│   ├── _features
│   │   └── features.md
│   ├── _highlights
│   │   └── highlights.md
│   ├── _intro
│   │   └── intro.md
│   ├── _overview
│   │   ├── grav-logo.png
│   │   └── overview.md
│   └── home.md

Theme File Structure

├── blueprints
│   └── modular
│       ├── highlights.yaml
│       └── showcase.yaml
├── css
│   └── main.css
├── fonts
├── imgs
├── js
├── sass
├── templates
│   ├── home.html.twig
│   ├── modular
│   │   ├── download.html.twig
│   │   ├── features.html.twig
│   │   ├── highlights.html.twig
│   │   ├── intro.html.twig
│   │   └── overview.html.twig
│   └── partials
│       └── base.html.twig
├── blueprints.yaml
├── onepager.yaml
├── screenshot.jpg
└── thumbnail.jpg

Config Files

site.yaml

The site’s configuration file. We should never edit the default configuration files found in system/config. Instead, we overwrite the settings we’d like to change via creating our own configuration files within user/config. Below is a simple version of a site configuration file.

For more complex configurations, have a look at the documentation.

title: 'One Pager'
author:
    name: 'Angie Vella'
    email: '[email protected]'
metadata:
    generator: 'Grav'
    description: 'Simple One Page Theme for Grav'
    keywords: 'HTML, CSS, Grav, Theme, One Page'
    author: 'Angie Vella'
    robots: 'noindex, nofollow'

Theme Files

As mentioned before, there are a couple of files Grav requires for a theme to function.

blueprints.yaml

This file defines theme information and configuration options to be shown in the Admin panel. Blueprints are defined with YAML and below is our blueprints file for our One Pager Theme.

name: One Pager
version: 1.0.0
description: "A simple one page theme for Grav"
author:
    name: Angie Vella
    email: [email protected]
    url: http://www.website.com
license: MIT

onepager.yaml

This file contains theme configuration, like the blueprints file this is also defined in YAML. For this example we’ll keep it super simple.

enabled: true
default_lang: en

Theme Templates

Now that we have the files the theme requires to function, it’s time to get started on the Twig templates. These templates will be used to render our content.

base.html.twig

The base template is just that: the template that will be the base of our theme. We’ll be extending this template within our other templates. Let’s break it down:

<html lang=”{{ theme_config.default_lang }}”> – Gets the configuration set in onepager.yaml.

{% block head %}{% endblock head %} – This defines an area in the base template, typically containing the stuff we put in the<head>element. Note that the head in {% endblock %} is not required, but can be used for readability.

<title>{{ site.title }}</title> – Pulls the configuration set in config/site.yaml.

<link rel=”canonical” href=”{{ page.url(true, true) }}”> – Sets a canonical URL for the page.

<link rel=”icon” type=”image/png” href=”{{ url(‘theme://img/favicon.png’) }}”> – Points to the site’s favicons, located in the theme/imgs directory.

{% block stylesheets %} – Within this block we register our stylesheets for the theme.

{{ assets.css() }} – This outputs the stylesheets we just registered.

{% block javascripts %} – Just like the stylesheets block this registers our JavaScript files for the theme. Note that the jQuery library comes bundled with Grav.

{{ assets.js() }} – Renders the scripts we just registered.

<a href=”{{ base_url}}”>OnePager</a> – Links to the home page.

{% for module in page.collection.modular() %}
    <li><a href="#{{ module.header.anchor }}">{{ module.menu }}</a></li>
{% endfor %}

Runs over the collection defined withinhome.mdto create the navigation.

{% block content %}{% endblock %} – This is where content from other templates that extend this one will be.

{% block bottom %}{% endblock %} – We add our custom JavaScript initialization here.

And this is what the file looks like when it’s all put together:

<!DOCTYPE html>
<html lang="{{ theme_config.default_lang }}">

    <head>
        {% block head %}
        <title>{{ site.title }}</title>

        <link rel="canonical" href="{{ page.url(true, true) }}">

        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <link rel="icon" type="image/png" href="{{ url('theme://img/favicon.png') }}">

        {% block stylesheets %}
            {% do assets.addCss('theme://css/main.css') %}
            {% do assets.addCss('https://fonts.googleapis.com/css?family=Montserrat:400,700') %}
        {% endblock %}

        {{ assets.css() }}

        {% block javascripts %}
            {% do assets.addJs('jquery', '110') %}

            {% do assets.addJs('theme://js/singlepagenav.min.js') %}

            {% if browser.getBrowser == 'msie' and browser.getVersion >= 8 and browser.getVersion <= 9 %}
                {% do assets.add('theme://js/html5shiv.min.js') %}
                {% do assets.add('theme://js/respond.min.js') %}
            {% endif %}
        {% endblock %}

        {{ assets.js() }}

        {% endblock head %}
    </head>

    <body>

        <header class="main-header">
            <div class="wrapper">
                <div class="logo">
                    <a href="{{ base_url}}">OnePager</a>
                </div>

                <nav class="main-nav">
                    <ul>
                        {% for module in page.collection.modular() %}
                            <li><a href="#{{ module.header.anchor }}">{{ module.menu }}</a></li>
                        {% endfor %}
                    </ul>
                </nav>
            </div>
        </header>

        <section class="content-wrapper">
            {% block content %}{% endblock %}
        </section>

        <footer class="main-footer">
            <p>&#169;<script type="text/javascript">document.write(new Date().getFullYear());</script> OnePager Grav Theme.</p>
        </footer>

        {% block bottom %}
            {{ assets.js('bottom') }}

            <script>
                $('.navigation-wrapper').singlePageNav({
                    offset: $('header.menu').outerHeight(),
                    updateHash: true,
                    currentClass: 'menu-active'
                });
            </script>
        {% endblock %}
    </body>
</html>

home.html.twig

The template used to render the home page (user/pages/01.home/home.md). It builds on top of the base template and adds anchors to the sections. These anchors will be defined within the modular markdown file’s Front Matter.

{% extends 'partials/base.html.twig' %}
{% block content %}
    {% for module in page.collection() %}
        <div id="{{ module.header.anchor }}"></div>

        {{ module.content }}
    {% endfor %}
{% endblock %}

intro.html.twig

The intro to our one page site. Simply pulls the content of the intro.md file using {{ content }}.

<div class="intro">
    <div class="wrapper">
        {{ content }}
    </div>
</div>

highlights.html.twig

The second section of our website. This pulls the content from the highlights.md file as well as the the custom header settings defined in the file’s YAML Front Matter (We’ll understand this better once we get into the content files).

<div class="highlights">
    <div class="wrapper">
        {{ content }}
        {% for highlight in page.header.highlights %}
            <div class="highlight">
                <i class="fa fa-fw fa-{{ highlight.icon }}"></i>

                <h4>{{ highlight.header }}</h4>
                <p>{{ highlight.text }}</p>
            </div>
        {% endfor %}
    </div>
</div>

overview.html.twig

By now you should start seeing a theme (no pun intended). This template renders the third section of the website. It pulls the content from overview.md and also get the image from the pages folder (section folder since this is a modular theme).

<div class="overview">
    <div class="wrapper">
        <div class="text">
            {{ content }}
        </div>

        <div class="image">
            {% set image = page.media.images|first %}
            {% if image %}
                {{ image }}
            {% endif %}
        </div>
    </div>
</div>

features.html.twig

Similar to the features template. This pulls the content defined within the features.md file and the file’s YAML Front Matter.

<div class="features">
    <div class="wrapper">
        {{ content }}
        {% for feature in page.header.features %}
            <div class="feature">
                <i class="fa fa-{{ feature.icon }}"></i>

                <h5>{{ feature.header }}</h5>
            </div>
        {% endfor %}
    </div>
</div>

download.html.twig

The last template of our theme, pulls the content from the download.md file and the button defined in the YAML Front Matter.

<div class="download">
    <div class="wrapper">
        {{ content }}

        {% for button in page.header.buttons %}
            <div class="button-wrapper">
                <a class="button" href="{{ button.url }}">{{ button.text }}</a>
            </div>
        {% endfor %}
    </div>
</div>

Content files

Our theme is now complete. We can focus on our content.

home.md

This file tells Grav which subpages to pull to assemble the modular page, and which order to display them in. The name of the file also tells Grav to use the home.html.twig template to render the page.

---
title: Home
content:
    items: '@self.modular'
    order:
        by: default
        dir: asc
        custom:
            - _intro
            - _highlights
            - _overview
            - _features
            - _download
---

intro.md

Within the YAML Front Matter we define the title of the section and an anchor. The content is then defined using markdown.

---
title: Home
anchor: intro
---

# OnePager
## A Simple One Page Modular Theme for Grav

highlights.md

Another title and anchor are defined, then we also create a custom header option called highlights. Each highlight has a header, text, and an icon. These are then rendered using the assigned template (highlights.html.twig).

Looking at the template you can see how the for statement {% for highlight in page.header.highlights %} loops through the highlights page header and then pulls the header {{ highlight.header }}, text {{ highlight.text }}, and icon {{ highlight.icon }}. At the end of this tutorial we’ll create a blueprint so these values can be edited via a form within the Admin panel.

---
title: Highlights
anchor: highlights
highlights:
    - header: 'Crazy Fast'
      text: 'Performance is not just an after thought, we baked it in from the start!'
      icon: fighter-jet
    - header: 'Easy to build'
      text: 'Simple text files means Grav is trivial to install, and easy to maintain.'
      icon: database
    - header: 'Awesome Technology'
      text: 'Grav employees best-in-class technologies such as Twig, Markdown &amp; Yaml'
      icon: cubes
    - header: 'Super Flexible'
      text: 'From the ground up, with many plugin hooks, Grav is extremely extensible'
      icon: puzzle-piece
---
### Built on top of Greatness
#### Four core tenants keep Grav focused

overview.md

Similar to the intro.md. Here we only define the title and anchor in the YAML Front Matter, together with the content in markdown syntax.

---
title: Overview
anchor: overview
---

### Fast, Extensible, Open Source!
#### Grav is a modern open source flat-file CMS
Grav is a modern open source flat-file CMS. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse suscipit ultrices ligula eget accumsan. Sed egestas augue a risus semper pretium non sit amet odio.

features.md

Here we basically repeat what we done in the highlights.md.

---
title: Features
anchor: features
features:
    - header: Markdown Syntax
      icon: text-height
    - header: Twig Templating
      icon: code
    - header: Smart Caching
      icon: rocket
    - header: Flexible Taxonomies
      icon: tags
    - header: Simple Install
      icon: cloud-download
    - header: Powerful Plugins
      icon: cogs
    - header: Intuitive UI
      icon: dashboard
    - header: File-Based
      icon: file-text
    - header: Documentation
      icon: bookmark
    - header: On Github
      icon: github
    - header: Responsive Design
      icon: html5
    - header: Awesomazing
      icon: heart
---

### Stuffed full of Amazing Features
#### This is a non-inclusive smattering of them

download.md

By now you’ve gotten the hang of the the YAML front matter. These headers can be as simple or as complicated as you want or need them to be. In this file we’ve defined the title and anchor for this section, together with a custom header defining our download button.

---
title: Download
anchor: download
buttons:
    - text: Download
      url: https://getgrav.org/downloads
---

### Get Grav

Blueprints

At this point our Grav theme is fully functioning, and we’ve also added the content for our one page website. Blueprints allow us to extend and modify the Admin panel. They are YAML files, defining a custom form for the specified template which then saves the data from the content file’s front matter.

Going over the files below, you can see they are very readable thanks to YAML. They extend on top of the default.yaml blueprint provided by Grav, and create a new tab in the Admin panel for each of the pages (in this case the highlights page and the features page).

highlights.yaml

title: Highlights
'@extends': default

form:
  fields:
    tabs:
      fields:
        advanced:
          fields:
            columns:
              fields:
                column1:
                  fields:
                    name:
                      default: modular/highlights
                      '@data-options': '\Grav\Common\Page\Pages::modularTypes'
            overrides:
              fields:
                header.template:
                  default: modular/highlights
                  '@data-options': '\Grav\Common\Page\Pages::modularTypes'
        highlights:
          type: tab
          title: Highlights
          fields:
            header.highlights:
              name: highlights
              type: list
              label: Highlights

              fields:
                .icon:
                  type: text
                  label: Icon
                .header:
                  type: text
                  label: Header
                .text:
                  type: text
                  label: Text

features.yaml

title: Features
'@extends': default

form:
  fields:
    tabs:
      fields:
        advanced:
          fields:
            columns:
              fields:
                column1:
                  fields:
                    name:
                      default: modular/features
                      '@data-options': '\Grav\Common\Page\Pages::modularTypes'
            overrides:
              fields:
                header.template:
                  default: modular/features
                  '@data-options': '\Grav\Common\Page\Pages::modularTypes'
        features:
          type: tab
          title: Features
          fields:
            header.features:
              name: features
              type: list
              label: Features

              fields:
                .icon:
                  type: text
                  label: Icon
                .header:
                  type: text
                  label: Header

Part Two – Mnmm blog theme

mnmm

Since much of the code is repeated from the previous theme, we will only go over the new code.

Content File Structure

├── 01.home
│   ├── _blog-post
│   │   ├── media.jpg
│   │   └── item.md
│   └── blog.md

Theme File Structure

├── css
│   └── main.css
├── fonts
├── imgs
├── js
├── sass
├── templates
│   ├── blog.html.twig
│   ├── error.html.twig
│   ├── item.html.twig
│   ├── modular
│   └── partials
│       ├── base.html.twig
│       └── blog_item.html.twig
├── blueprints.yaml
├── mnmm.php
├── mnmm.yaml
├── screenshot.jpg
└── thumbnail.jpg

Plugins

In the previous theme, we only used the plugins that come with the Grav Core + Admin Panel. For this theme we’ve added some more plugins;

  • Feed – Lets you view a Grav Collection as RSS or Atom news feed.
  • JS Comments – Enables you to add comments to your site with Discourse, Disqus, Facebook, Google+, IntenseDebate, and Muut comment systems.
  • Pagination – Adds pagination to your site.
  • Readingtime – Adds reading time to your pages.
  • SimpleSearch – Enables you to search your site’s content.

Config Files

site.yaml

Our config files remain the same, the only thing that changed is the title in site.yaml.

Theme Files

blueprints.yaml

The Front Matter here was changed to reflect this theme.

mnmm.yaml

This file is completely unchanged.

Theme templates

base.html.twig

The base template file is very similar to the previous theme. The only changes here are for content/aesthetic reasons. The Grav code remains the same and is explained it the previous section.

<!DOCTYPE html>
<html lang="{{ theme_config.default_lang }}">

    <head>
        {% block head %}
        <title>{{ site.title }}</title>

        <link rel="canonical" href="{{ page.url(true, true) }}">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="icon" type="image/png" href="{{ url('theme://img/favicon.png') }}">

        {% block stylesheets %}
            {% do assets.addCss('theme://css/main.css') %}
            {% do assets.addCss('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700') %}
        {% endblock %}

        {{ assets.css() }}

        {% block javascripts %}
            {% do assets.addJs('jquery', '110') %}

            {% do assets.addJs('theme://js/singlepagenav.min.js') %}

            {% if browser.getBrowser == 'msie' and browser.getVersion >= 8 and browser.getVersion <= 9 %}
                {% do assets.add('theme://js/html5shiv.min.js') %}
                {% do assets.add('theme://js/respond.min.js') %}
            {% endif %}
        {% endblock %}

        {{ assets.js() }}

        {% endblock head %}
    </head>

    <body>

        <header class="main-header">
            <nav class="main-nav">
                <div class="wrapper">
                    <div class="logo"><a href="{{ base_url}}">MNMM</a></div>

                    <div class="search">
                        {% if config.plugins.simplesearch.enabled %}
                            {% include 'partials/simplesearch_searchbox.html.twig' %}
                        {% endif %}
                    </div>
                </div>
            </nav>

            <div class="site-title">
                <div class="wrapper">
                    <h1>{{ site.title }}</h1>
                </div>
            </div>
        </header>

        <section class="main-wrapper">
            {% block content %}{% endblock %}
        </section>

        {% block footer %}
        <footer class="main-footer">
            <div class="wrapper">
                <span class="credits">&#169;<script type="text/javascript">document.write(new Date().getFullYear());</script> MNMM Grav Theme.</span>

                <span class="icons">
                    <a class="button" href=""><i class="fa fa-facebook-square"></i></a>
                    <a class="button" href=""><i class="fa fa-twitter-square"></i></a>
                    <a class="button" href=""><i class="fa fa-google-plus-square"></i></a>
                    <a class="button" href=""><i class="fa fa-pinterest-square"></i></a>
                    <a class="button" href=""><i class="fa fa-linkedin-square"></i></a>
                    <a class="button" href=""><i class="fa fa-instagram"></i></a>
                    <a class="button" href=""><i class="fa fa-github-square"></i></a>

                    {% if config.plugins.feed.enabled %}
                        <a class="button" href="{{ feed_url }}.atom"><i class="fa fa-rss-square"></i></a>
                        <!--<a class="button" href="{{ feed_url }}.rss"><i class="fa fa-rss-square"></i> RSS</a>-->
                    {% endif %}
                </span>
            </div>
        </footer>
        {% endblock %}

        {% block bottom %}
            {{ assets.js('bottom') }}
        {% endblock %}
    </body>
</html>

blog.html.twig

This template is use to render the blog listings page, which in this case is the home page. It loops through the child pages (blog items) and includes the blog_item.html.twig template as well as pagination.html.twig which adds the blog listings page navigation.

{% embed 'partials/base.html.twig' %}

    {% set collection = page.collection() %}

    {% block content %}
        <div class="blog-list-wrapper">
            <div class="listing">
                {% for child in collection %}
                    {% include 'partials/blog_item.html.twig' with {'blog':page, 'page':child, 'truncate':true} %}
                {% endfor %}

                <div class="pagination-wrapper">
                    {% if config.plugins.pagination.enabled and collection.params.pagination %}
                        {% include 'partials/pagination.html.twig' with {'base_url':page.url, 'pagination':collection.params.pagination} %}
                    {% endif %}
                </div>
            </div>
        </div>
    {% endblock %}
{% endembed %}

error.html.twig

This template is rendered when a page is not found. The error plugin is used which is included with the Grav Core + Admin Panel package.

{% extends 'partials/base.html.twig' %}
{% block content %}
    <div id="error">
        <div>
            <h2>{{ 'ERROR'|t }} {{ page.header.http_response_code }}</h2>
            <p>
                {{ page.content }}
            </p>
        </div>
    </div>
{% endblock %}

item.html.twig

The item template renders the blog item content as specified in the blog.md file.

{% embed 'partials/base.html.twig' %}

    {% block content %}
        <div class="blog-item-wrapper">
            <div id="blog-item">
                {% include 'partials/blog_item.html.twig' with {'blog':page.parent, 'truncate':false} %}
            </div>
        </div>
    {% endblock %}
{% endembed %}

blog_item.html.twig

The template that renders the blog item. In here we pull the date, post title, post content, reading time (using the Reading Time Plugin), post tags, social share buttons (using the Social Buttons Plugin), comments (using the JSComments Plugin), and post pagination.

<div class="blog-list-item">

    <div class="blog-list-header">
        <span class="blog-list-date">
            <time class="date" datetime="{{ page.date|date("c") }}">
                <span>{{ page.date|date("d") }}</span>
                <em>{{ page.date|date("M") }}</em>
                <em>{{ page.date|date("Y") }}</em>
            </time>
        </span>

        {% if page.header.link %}
            <h4 class="post-name">
                {% if page.header.continue_link is not sameas(false) %}
                <a href="{{ page.url }}"><i class="fa fa-angle-double-right u-url"></i></a>
                {% endif %}
                <a href="{{ page.header.link }}" class="u-url">{{ page.title }}</a>
            </h4>
        {% else %}
            <h4 class="post-name"><a href="{{ page.url }}" class="u-url">{{ page.title }}</a></h4>
        {% endif %}
    </div>

    <div class="list-blog-padding">
        {% if page.header.continue_link is sameas(false) %}
            <div class="post-content">
                {{ page.content }}
            </div>
            {% if not truncate %}
            {% set show_prev_next = true %}
            {% endif %}
        {% elseif truncate and page.summary != page.content %}
            <div class="post-content">
                {{ page.summary }}
            </div>

            <div class="meta">
                {% if config.plugins.readingtime.enabled %}
                    <span class="reading">{{ page.content|readingtime }}</span>
                {% endif %}

                {% if page.taxonomy.tag %}
                    <span class="tags">
                        {% for tag in page.taxonomy.tag %}
                            <a href="{{ blog.url|rtrim('/') }}/tag{{ config.system.param_sep }}{{ tag }}">{{ tag }}</a>
                        {% endfor %}
                    </span>
                {% endif %}
            </div>
        {% elseif truncate %}
            <div class="post-content">

                {% if page.summary != page.content %}
                    {{ page.content|truncate(550) }}
                {% else %}
                        {{ page.content }}
                {% endif %}
            </div>
        {% else %}
            <div class="post-content">
                {{ page.content }}
            </div>

            <div class="post-meta">
                {% if page.taxonomy.tag %}
                    <span class="post-tags">
                        {% for tag in page.taxonomy.tag %}
                            <a href="{{ blog.url|rtrim('/') }}/tag{{ config.system.param_sep }}{{ tag }}">{{ tag }}</a>
                        {% endfor %}
                    </span>
                {% endif %}
            </div>

            <div class="post-share">
                {% if config.plugins.socialbuttons.enabled %}
                    {% include 'partials/socialbuttons.html.twig' with {'url':page.url} %}
                {% endif %}
            </div>

            <div class="post-comments">
                {% if config.plugins.jscomments.enabled %}
                    {{ jscomments() }}
                {% endif %}
            </div>
        {% set show_prev_next = true %}
    {% endif %}
    </div>
</div>
{% if show_prev_next %}
    <div class="post-pagination">
        <div class="wrapper">
            {% if not page.isFirst %}
                <span class="lft"><a class="button" href="{{ page.nextSibling.url }}"><i class="fa fa-chevron-left"></i> Next</a></span>
            {% endif %}

            {% if not page.isLast %}
                <span class="rgt"><a class="button" href="{{ page.prevSibling.url }}">Prev <i class="fa fa-chevron-right"></i></a></span>
            {% endif %}
        </div>
    </div>
{% endif %}

Content files

blog.md

items: ‘@self.children’ – tells Grav to loop through the page’s subpages (the blog posts).

order: – Order configuration for the blog posts.

pagination – enables the pagination.

---
title: 'MNMM Grav Theme'
content:
    items: '@self.children'
    order:
        by: date
        dir: desc
    limit: 5
    pagination: true
feed:
    description: 'Sample Blog Description'
    limit: 10
pagination: true
---

# MNMM Grav Theme

item.md

This is what the blog post files look like. A title and date are specified, as well as taxonomy. In this case we add this post to the blog category and define its tags. After the YAML Front Matter, the blog post content is defined.

---
title: The Urban Jungle
date: 17:34 07/04/2014
taxonomy:
    category: blog
    tag: [travel, photography, city]
---

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies tristique nulla et mattis. Phasellus id massa eget nisl congue blandit sit amet id ligula. Praesent et nulla eu augue tempus sagittis. Mauris faucibus nibh et nibh cursus in vestibulum sapien egestas. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc. Mauris mattis nunc sed mi sagittis et facilisis tortor volutpat. Etiam tincidunt urna mattis erat placerat placerat ac eu tellus.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies tristique nulla et mattis. Phasellus id massa eget nisl congue blandit sit amet id ligula. Praesent et nulla eu augue tempus sagittis. Mauris faucibus nibh et nibh cursus in vestibulum sapien egestas. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc. Mauris mattis nunc sed mi sagittis et facilisis tortor volutpat. Etiam tincidunt urna mattis erat placerat placerat ac eu tellus. Ut nec velit id nisl tincidunt vehicula id a metus. Pellentesque erat neque, faucibus id ultricies vel, mattis in ante. Donec lobortis, mauris id congue scelerisque, diam nisl accumsan orci, condimentum porta est magna vel arcu. Curabitur varius ante dui. Vivamus sit amet ante ac diam ullamcorper sodales sed a odio. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies tristique nulla et mattis. Phasellus id massa eget nisl congue blandit sit amet id ligula. Praesent et nulla eu augue tempus sagittis. Mauris faucibus nibh et nibh cursus in vestibulum sapien egestas. Curabitur ut lectus tortor. Sed ipsum eros, egestas ut eleifend non, elementum vitae eros. Mauris felis diam, pellentesque vel lacinia ac, dictum a nunc. Mauris mattis nunc sed mi sagittis et facilisis tortor volutpat. Etiam tincidunt urna mattis erat placerat placerat ac eu tellus. Ut nec velit id nisl tincidunt vehicula id a metus. Pellentesque erat neque, faucibus id ultricies vel, mattis in ante. Donec lobortis, mauris id congue scelerisque, diam nisl accumsan orci, condimentum porta est magna vel arcu. Curabitur varius ante dui. Vivamus sit amet ante ac diam ullamcorper sodales sed a odio.

Conclusion

After building these two themes, you should have a basic understanding of how Grav works. As I’ve already said, in my opinion Grav can be as simple or as complex as you want/need it to be. I look forward to seeing some awesome things being built with this is awesome platform!

Angie Vella is a freelance graphic and web designer based in Malta. More articles by Angie Vella
  • hexplor

    I use grav for every project for like 2 years. I’m never coming back to WordPress or Joomla. Grav is fast and flexible and allows me to configure my cms the way i can.

Home CSS Deals HTML HTML5 Java JavaScript jQuery Miscellaneous Mobile MySQL News PHP Resources Security Snippet Tools Tutorial Web Development Web Services WordPress