Create Stylish Wagtail Pages with Tailwind CSS

Table of Contents

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Objectives

By the end of this chapter, you should be able to:

  1. Build page layout with Tailwind CSS
  2. Understand Django template-inheritance
  3. Build custom Django template tag

Prerequisites

Before start, please make sure you already setup Tailwind CSS by following Modern Frontend Techs for Wagtail

Design

BlogPage

PostPage

Base Template

Let's create wagtail_app/templates/base.html

{% load static wagtailuserbar webpack_loader %}

<!DOCTYPE html>
<html class="h-100" lang="en">
  <head>
    <meta charset="utf-8" />
    <title>
        {% block title %}
            {% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}
        {% endblock %}
        {% block title_suffix %}
            {% with self.get_site.site_name as site_name %}
                {% if site_name %}- {{ site_name }}{% endif %}
            {% endwith %}
        {% endblock %}
    </title>
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    {# Global stylesheets #}
    {% stylesheet_pack 'app' %}

    {% block extra_css %}
      {# Override this in templates to add extra stylesheets #}
    {% endblock %}

  </head>

  <body class="{% block body_class %}{% endblock %}">
    <div class="flex flex-col min-h-screen">

      {% wagtailuserbar %}

      {% include 'blog/components/navbar.html' %}

      <div class="w-full max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
        <div class="flex flex-row flex-wrap py-4">
          <main role="main" class="w-full sm:w-2/3 md:w-3/4 lg:w-8/12 px-2 mb-4">
            {% block content %}{% endblock %}
          </main>
          {% include 'blog/components/sidebar.html' %}
        </div>
      </div>

      {% include 'blog/components/footer.html' %}

    </div>
  </body>

  {# Global javascript #}
  {% javascript_pack 'app' %}

  {% block extra_js %}
    {# Override this in templates to add extra javascript #}
  {% endblock %}

</html>

Notes:

  1. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override
  2. With Django include template tag, we can loads a template and renders it with the current context. Django doc

Let's create wagtail_app/templates/blog/components/navbar.html, which is loaded by {% include 'blog/components/navbar.html' %} in the wagtail_app/templates/base.html

<nav class="bg-white border-b border-opacity-75 border-gray-300 dark:bg-gray-900 dark:text-white">
  <div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
    <div class="relative flex items-center justify-between h-16">

      <div class="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
        <div class="flex-shrink-0 flex items-center">
          <a href="/"><span class="text-bold text-grey-800">Wagtail Blog Demo</span></a>
        </div>
        <div class="hidden sm:block sm:ml-6">
          <div class="flex space-x-4">
            <a href="#"
               class="text-gray-500 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Dashboard</a>
            <a
              href="#"
              class="text-gray-500 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Team</a>
            <a
              href="#"
              class="text-gray-500 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Projects</a>
            <a
              href="#"
              class="text-gray-500 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Calendar</a>
          </div>
        </div>
      </div>

    </div>
  </div>
</nav>
  1. We will update navbar.html in a bit, now it only works on the desktop browser.

Create wagtail_app/templates/blog/components/footer.html which is loaded by {% include 'blog/components/footer.html' %} in the wagtail_app/templates/base.html

<footer
  class="mt-auto py-7 bg-white border-t border-opacity-75 border-gray-300">
  <div class="container mx-auto">
    <p class="m-0 text-center text-blue-700 hover:underline">
      Built by <a href="https://www.accordbox.com/">[email protected]</a>
    </p>
  </div>
</footer>

Create wagtail_app/templates/blog/components/sidebar.html which is loaded by {% include 'blog/components/sidebar.html' %} in the wagtail_app/templates/base.html

<div class="w-full sm:w-1/3 md:w-1/4 lg:w-4/12 px-2">

  <div>Sidebar</div>

</div>

BlogPage

Update wagtail_app/templates/blog/blog_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}

  {% for post in page.get_children.specific %}
    <div class="mb-4 rounded-lg border border-opacity-75 border-gray-300 shadow-xl overflow-hidden">
      {% if post.header_image %}
        {% image post.header_image original as header_image %}
        <a href="{% pageurl post %}">
          <img src="{{ header_image.url }}">
        </a>
      {% endif %}

      <div class="p-6">
        <h2 class="title-font text-3xl text-blue-900 mb-6">
          <a href="{% pageurl post %}">{{ post.title }}</a>
        </h2>
        <p>
          {{ post.search_description }}
        </p>
        <a href="{% pageurl post %}" class="px-4 py-3 text-white bg-blue-500 border border-blue-500 hover:bg-blue-600 rounded-lg">Read More &rarr;</a>
      </div>

      <div class="bg-gray-100 px-6 py-4">
        <h4 class="text-base text-gray-900">Posted on {{ post.last_published_at }}</h4>
      </div>

    </div>
  {% endfor %}

{% endblock %}

Notes:

  1. With Django extends template tag, we start using Django template-inheritance.
  2. The HTML in {% block content %} would override {% block content %} in wagtail_app/templates/base.html, you can check Django doc: Template inheritance to learn more
# please make sure `npm run start` is running in another terminal

$ docker-compose up -d
$ docker-compose logs -f

If we check the blog page, it looks like

PostPage

Update wagtail_app/templates/blog/post_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}

  {% image page.header_image original as header_image %}
  <img src="{{ header_image.url }}" alt="{{ header_image.alt }}">

  <h1>{{ page.title }}</h1>

{% endblock %}

If we check the post page, it looks like

Category Widget

Next, let's add Category widget to the sidebar.

To keep the template clean, we will create a custom Django template tag to do this.

$ mkdir wagtail_app/blog/templatetags
$ touch wagtail_app/blog/templatetags/__init__.py

Create wagtail_app/blog/templatetags/blogapp_tags.py

from ..models import BlogCategory, Tag
from django.template import Library, loader

register = Library()


@register.inclusion_tag('blog/components/categories_list.html',
                        takes_context=True)
def categories_list(context):
    categories = BlogCategory.objects.all()
    return {
        'request': context['request'],
        'categories': categories
    }

Notes:

  1. The logic is very simple, we get all BlogCategory instances from the DB and display it in template.
  2. The blog/components/categories_list.html is the template which will be used to render HTML
  3. You can check Custom template tags and filters

Create wagtail_app/templates/blog/components/categories_list.html

<div class="mb-4 border rounded-lg border-opacity-75 border-gray-300 shadow-xl overflow-hidden ">
  <div class="bg-gray-100 text-gray-900 px-6 py-4 ">
    <h4 class="text-base font-medium">Categories</h4>
  </div>
  <div class="px-6 py-4">
    <nav class="list-none">
      {% for category in categories %}
        <li>
          <a href="#" class="text-gray-600 hover:text-gray-800 no-underline hover:underline ">
            {{ category.name }}
          </a>
        </li>
      {% empty %}
        'No categories yet'
      {% endfor %}
    </nav>
  </div>
</div>

Tag Widget

Update wagtail_app/blog/templatetags/blogapp_tags.py

@register.inclusion_tag('blog/components/tags_list.html',
                        takes_context=True)
def tags_list(context):
    tags = Tag.objects.all()
    return {
        'request': context['request'],
        'tags': tags
    }

Create wagtail_app/templates/blog/components/tags_list.html

<div class="mb-4 border rounded-lg border-opacity-75 border-gray-300 shadow-xl overflow-hidden">
  <div class="bg-gray-100 text-gray-900 px-6 py-4">
    <h4 class="text-base font-medium">Tags</h4>
  </div>
  <div class="px-6 py-4">

    {% for tag in tags %}
      <a href="#" class="text-gray-600 hover:text-gray-800">
        <span class="inline-flex items-center justify-center px-2 py-1 mr-2 text-xs font-bold leading-none text-white bg-gray-600 hover:bg-gray-500 focus:bg-gray-700 rounded-full">{{ tag }}</span>
      </a>
    {% empty %}
      No tags yet
    {% endfor %}

  </div>
</div>

After we build tags_list and categories_list, let's update wagtail_app/templates/blog/components/sidebar.html

{% load blogapp_tags %}

<div class="w-full sm:w-1/3 md:w-1/4 lg:w-4/12 px-2">

  {% categories_list %}

  {% tags_list %}

</div>

Notes:

  1. At the top, we load blogapp_tags so the above template tags would be available in the sidebar.html (Like import statement in Python)
  2. We call categories_list and tags_list to render the category list and tag list on the sidebar.

As you can see, now the Category widget and Tag widget are working in the sidebar.

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2024 AccordBox