Themes Documentation



Using a pre-existing theme in Bestatic is very easy (you have probably already figured that out). Creating a theme for Bestatic takes a little bit of effort, but actually, you just have to follow a few simple steps.

Let's go!



Using a theme

It involves three steps:

  1. Download a theme from the GitHub repo. You can find direct download link (in zip format) from our theme listing page. After downloading the .zip file, unzip/extract the contents of it.

  2. In the root directory of your site, create a themes folder. Then create a folder with theme name, such as "Amazing". Put all the content of themes inside that folder (i.e., all the files in static and templates directories). You can look at directory structure section for example.

    The directory structure should look something like this:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    ```
    
    mysite/
    ├── themes/
    │   └── Amazing/
    │       ├── static/
    │       ├── templates/
    │       └── ...
    
    ```
    

    Note that, the static and templates directories are required.

  3. Update the config file with theme key, for example, theme: Amazing. You can read more about the config file here. You can also supply this as a command line argument, such as -t Amazing or --theme Amazing. See details here.

That's it!



Using a theme without a home.html.jinja2 template

If you are using a theme that does not come with a home page template (i.e., home.html.jinja2 template), you need to create a home.md file in the pages directory (./pages/home.md) and use slug: index.html in the frontmatter. This file should have the following content (Note that the template parameter is optional and should be present only if you are using a template other than regular page template (i.e., page.html.jinja2)):

Example home.md file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
---
title: Homepage
description: The home page for the website
slug: index.html
template: page.html.jinja2 # This parameter is optional. 
---

### Some heading here

This is the home page. Some more text here.

Some **bold** text is here. And here is a [link](https://www.bestaticpy.com).



Note on Quickstart

Note that if you are using a theme that does not come with a home page template (i.e., home.html.jinja2 file) for quickstart (i.e., using bestatic quickstart -t <theme-name> command), you need to create a home.md file in the pages directory and use slug: index.html in the frontmatter. Please See example above.

If you do not create this file before quickstart, you would get this error:No such file or directory: '_output/index.html'


Note on renaming home.html.jinja2 template

Some themes (such as bestatic-lab-group and bestatic-lab-group-simple) have a home.html.jinja2 template, but it is better to rename it to something like homepage.html.jinja2, create a home.md file in the pages directory (or its subdirectories) to create a homepage, put the content in it, and then use template: homepage.html.jinja2 and slug: index.html in the frontmatter. You can then use the .splitsection class in the home.md markdown file to split the content into sections. This way you can minimize writing raw HTML code and use simple markdown. See the example sites here and here.



Creating a theme

A themes directory structure would typically look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
└── themes
    └── Amazing
        ├── static
        │   ├── Images
        │   │   ├── Bottom logo.png
        │   │   ├── Logo_1.png
        │   │   ├── Young1.png
        │   │   └── favicon.ico
        │   ├── css
        │   │   ├── banerstyle.css
        │   │   └── styles.css
        │   └── js
        │       ├── fuse-search-modal-ui.js
        │       └── vanilla-back-to-top.min.js
        └── templates
            ├── 404.html.jinja2
            ├── home.html.jinja2
            ├── layout.html.jinja2
            ├── list.html.jinja2
            ├── page.html.jinja2
            ├── partials
            │   ├── footer.html.jinja2
            │   ├── head.html.jinja2
            │   ├── header.html.jinja2
            │   ├── katex.html.jinja2
            │   └── nav.html.jinja2
            ├── post.html.jinja2
            └── taglist.html.jinja2

As you can see, to create a new theme for Bestatic, you primarily need to create two subdirectories: a) static and b) templates.

  • The static folder should contain all of your...well, "static" files, i.e. the files that do not need to directly processed by Bestatic system, but still essential for correct functioning of the generated website. All of your Images, Javascript, CSS, etc. files should be included in this folder. When Bestatic generates your website, it will copy this into _output/static path (or into <your choice of directory name>/static path) in a nested fashion, maintaining directory structure.

  • Now the templates folder. This should contain all of your page templates. Bestatic, a program written in Python, naturally uses Jinja2 based templates. Extensive Jinja2 documentation is available. You can actually learn the basics of Jinja2, such as variables, if-elif-else conditions, for loop, etc. in 10 min from here.

    Once you have done learned those, please feel free to start playing with Jinja2 blocks within this (make sure to save the files with extension .html.jinja2):

    1
    2
    3
    4
    5
    {% block something %}
    .
    .
    .
    {% endblock %}
    


    Now, a couple of things to remember here while designing theme for Bestatic:

    • You are free to organize your partials in any way you prefer (although we recommend putting them in ./themes/theme-name/templates/partials directory). For reference, "partials" are parts of HTML codes that you can typically reuse to build different templates, such as head, header, footer, navbar, etc.

    • We recommend (but not required) using layout.html.jinja2 (and layout2.html.jinja2, layout3.html.jinja2 etc., for complex themes) to assemble these partials. A very simple layout file may look like this:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      <!DOCTYPE html>
      <html lang="en">
      <head>
      {% include 'partials/head.html.jinja2' %}
      
      {% if condition %}
      ... 
      {% endif %}
      
      <meta name="description" content="{% block description %}{{ description }}{% endblock %}"/>
      <title>{% block title %}{{ title }}{% endblock %}</title>
      </head>
      
      <body>
      
      {% include 'partials/nav.html.jinja2' %}
      
      <div class="container">
      
      {% block content %} {% endblock %}
      
      </div>
      <br>
      
      {% include 'partials2/footer.html.jinja2' %}
      
      </body>
      
      </html>
      

      For this file, you can modify the title, description, and content as you wish. We recommend building new files such as post.html.jinja2, page.html.jinja2, etc. by extending layout files like this. You can start those files with {% extends "layout.html.jinja2" %} and then provide your custom title, description, content, etc. and you are good to go!

    • A few things to remember regarding how Bestatic passes variables to Jinja2 templates:

      • The main title of the site and description (that you can mention in bestatic.yaml file or from Bestatic CLI) is provided as title and description respectively.

      • The type of the page is provided with typeof variable: It can be home (for separate homepage; also see homepage customization for relevant details), posts (for separate blog post pages), lists (for blog post listing pages), or tags (for tag-based blog post listing pages).

      • Regarding blog-posts: Disqus shortname or Giscus repo id (as mentioned in bestatic.yaml file) are provided as disqus and giscus variables, respectively. Tags (for taglist pages) are provided as tags variable.

      • All information regarding pages or posts can be accessed by page.content format or post.content (note: .content can be replaced by .metadata,.summary, .tags, .katex, .text, .title, .slug, or .path_info).

      • All information provided in post/page front matter will be available as post.metadata or page.metadata which can be further extracted as post.metadata["date"], post.metadata["title"], etc.

      • Few more available variables: next_slug (useful for linking next post in blog page), prev_slug (useful for linking previous post in blog page), page_index, and page_range (these two provide information on number of blog listing pages, which can be used to dynamically create blog page index like Blog Page 2 of 21).

      • Finally, Bestatic expects six templates to be present in templates directory: post.html.jinja2, page.html.jinja2, home.html.jinja2, 404.html.jinja2, list.html.jinja2, and taglist.html.jinja2. The Last two are necessary if you have a blog section for your website or if your website is a blog. The 404.html.jinja2 can be skipped if custom 404 page is not required. Others can be skipped if user provide template: key in their page/post front matter or prefers different approach of home page (see here and here for more details).

For a couple of more advanced examples, please see the Example templates section below.



Example templates

Let's take a look at an example of a multi-column layout template and also an example of how to use data files in your templates.

These are advanced examples where we are using Bootstrap's responsive grid system to create layouts that adapt to different screen sizes.

Example markdown file:

First, create your data file at _includes/datafiles/members.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
- name: John Doe, PhD
  role: Software Engineer
  email: john.doe@example.com
  expertise: Machine Learning and Computer Vision

- name: Jane Smith, MSc
  role: Research Scientist
  email: jane.smith@example.com
  expertise: Natural Language Processing

- name: Bob Johnson
  role: Data Analyst
  email: bob.johnson@example.com
  expertise: Statistical Analysis and Visualization

Then create your page at pages/team.md:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---
title: Our Team
slug: team
description: Meet our talented team members
template: team.html.jinja2
section: true
data_files: members.yaml
---

#### Team Members {.splitsection}

We're a diverse group of researchers and engineers working on cutting-edge projects.

{[!data!]}

#### Join Us {.splitsection}

Interested in joining our team? Check out our [careers page](/careers).

Key points about the markdown file:

  • section: true enables section splitting based on headings with .splitsection class
  • data_files: members.yaml specifies which data files to use (comma-separated for multiple files)
  • {[!data!]} marker tells the template where to insert the data
  • Sections without {[!data!]} will display only content (no heading)
  • The template processes data files sequentially - first {[!data!]} uses first file, second uses second file, etc.

Basic 2-column layout template (team.html.jinja2):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{% extends "layout.html.jinja2" %}
{% block title %}{{ page.metadata["title"] }} | {{ title }}{% endblock %}
{% block content %}

<div class="container">
    <h3>{{ page.metadata["title"] }}</h3>

    {% if sections %}
        {# Parse data_files from frontmatter #}
        {% set data_file_list = [] %}
        {% if page.metadata.data_files %}
            {% for df in page.metadata.data_files.split(',') %}
                {% set _ = data_file_list.append(df.strip().replace('.yaml', '')) %}
            {% endfor %}
        {% endif %}

        {% set data_file_index = namespace(value=0) %}

        {% for section in sections %}
            {% set has_data = '{[!data!]}' in section.content | join('') %}

            {% if has_data %}
                <h4>{{ section.heading }}</h4>
            {% endif %}

            {# Display content without marker #}
            {% for content in section.content %}
                {% if '{[!data!]}' not in content %}
                    {{ content | safe }}
                {% endif %}
            {% endfor %}

            {# Render data in 2-column grid #}
            {% if has_data and data_file_index.value < data_file_list | length %}
                {% set file_name = data_file_list[data_file_index.value] %}
                <div class="row">
                    {% for member in data_files[file_name] %}
                        <div class="col-12 col-md-6 mb-4">
                            <div class="card p-3">
                                <h5>{{ member.name }}</h5>
                                <p><strong>{{ member.role }}</strong></p>
                                <p>Email: <a href="mailto:{{ member.email }}">{{ member.email }}</a></p>
                                <p>{{ member.expertise }}</p>
                            </div>
                        </div>
                    {% endfor %}
                </div>
                {% set data_file_index.value = data_file_index.value + 1 %}
            {% endif %}
        {% endfor %}
    {% endif %}
</div>

{% endblock %}

This creates a layout that is 1 column on mobile (col-12) and 2 columns on medium+ screens (col-md-6).

Example with multiple data files:

If you have multiple types of team members, create separate data files and sections:

pages/team.md:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
title: Our Team
template: team.html.jinja2
section: true
data_files: faculty.yaml, students.yaml, alumni.yaml
---

#### Faculty {.splitsection}

Our experienced faculty members lead groundbreaking research.

{[!data!]}

#### Graduate Students {.splitsection}

Talented students working on innovative projects.

{[!data!]}

#### Alumni {.splitsection}

Where are they now? Our alumni have gone on to amazing careers.

{[!data!]}

Each {[!data!]} marker will use the next file in the data_files list sequentially.

Responsive 2/3-column layout:

For more control over responsive behavior, create separate layouts for different screen sizes. This example shows 2 columns on mobile/tablet and 3 columns on desktop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
{# Process data with {[!data!]} marker #}
{% if has_data and data_file_index.value < data_file_list | length %}
    {% set file_name = data_file_list[data_file_index.value] %}
    {% set items = data_files[file_name] %}
    {% set total_items = items | length %}

    {# Calculate rows for each layout #}
    {% set lg_cols = 3 %}  {# 3 columns for large screens #}
    {% set sm_cols = 2 %}  {# 2 columns for small screens #}

    {% set lg_rows = (total_items / lg_cols) | int %}
    {% set lg_remaining = total_items % lg_cols %}
    {% set sm_rows = (total_items / sm_cols) | int %}
    {% set sm_remaining = total_items % sm_cols %}

    {# Small/Medium screens: 2 columns #}
    <div class="d-lg-none">
        {% for row in range(sm_rows) %}
            <div class="row mb-4">
                {% for i in range(sm_cols) %}
                    <div class="col-12 col-sm-6 px-3 mb-4">
                        {% set item = items[row * sm_cols + i] %}
                        <div class="card">
                            <h5>{{ item.name }}</h5>
                            <p>{{ item.role }}</p>
                        </div>
                    </div>
                {% endfor %}
            </div>
        {% endfor %}

        {# Handle remaining items #}
        {% if sm_remaining > 0 %}
            <div class="row justify-content-center mb-4">
                {% for i in range(sm_remaining) %}
                    <div class="col-12 col-sm-6 px-3 mb-4">
                        {% set item = items[sm_rows * sm_cols + i] %}
                        <div class="card">
                            <h5>{{ item.name }}</h5>
                            <p>{{ item.role }}</p>
                        </div>
                    </div>
                {% endfor %}
            </div>
        {% endif %}
    </div>

    {# Large screens: 3 columns #}
    <div class="d-none d-lg-block">
        {% for row in range(lg_rows) %}
            <div class="row mb-4">
                {% for i in range(lg_cols) %}
                    <div class="col-4 px-3 mb-4">
                        {% set item = items[row * lg_cols + i] %}
                        <div class="card">
                            <h5>{{ item.name }}</h5>
                            <p>{{ item.role }}</p>
                        </div>
                    </div>
                {% endfor %}
            </div>
        {% endfor %}

        {# Handle remaining items #}
        {% if lg_remaining > 0 %}
            <div class="row justify-content-center mb-4">
                {% for i in range(lg_remaining) %}
                    <div class="col-4 px-3 mb-4">
                        {% set item = items[lg_rows * lg_cols + i] %}
                        <div class="card">
                            <h5>{{ item.name }}</h5>
                            <p>{{ item.role }}</p>
                        </div>
                    </div>
                {% endfor %}
            </div>
        {% endif %}
    </div>

    {% set data_file_index.value = data_file_index.value + 1 %}
{% endif %}

How the template processes markdown with .splitsection:

  1. The template reads section: true from frontmatter and splits content by headings with .splitsection class
  2. For each section, it checks if {[!data!]} marker exists
  3. If found, it displays the heading, content (without marker), and renders the data file
  4. Data files are processed sequentially from the data_files frontmatter list
  5. Sections without {[!data!]} display only their content (useful for text-only sections)

Key Bootstrap concepts:

  • d-lg-none: Hide on large screens and above (shows on mobile/tablet)
  • d-none d-lg-block: Hide on small/medium, show on large screens
  • col-12 col-sm-6: Full width on mobile, half width on small+ screens
  • col-4: One-third width (3 columns)
  • justify-content-center: Center remaining items that don't fill a row
  • px-3, mb-4: Bootstrap spacing utilities for padding and margins

Bootstrap grid classes:

  • col-12: Full width (1 column)
  • col-6: Half width (2 columns)
  • col-4: One-third width (3 columns)
  • col-3: One-quarter width (4 columns)
  • col-sm-*, col-md-*, col-lg-*, col-xl-*: Responsive breakpoints


That is all! If you are facing any issue to create a theme, search using your favorite search engine, ask your favorite LLM/AI assistant, or talk with us over our GitHub discussions page. We would love to help. Thank you for trying out Bestatic!!