Skip to content

Writing custom themes of MkDocs

One way of inserting metadata in pages rendered by MkDocs is via a custom theme. This is relevant for metadata that will be the same throughout the site.

Custom themes also offer enhanced options for changing layout and appearance of CCM.

Customize existing theme

Would be hard work to start from scratch an build a full theme, so better to start with one of the built-in themes. Simplest form of extension is simply to create custom stylesheet in file(s) specified by extra_css: block in mkdocs.yml. A more useful/extensive approach is to modify the template used to render the markdown into html.

MkDocs user guide on customizing a theme

A local template is written in jinja, called main.html put in directory that is not child of docs_dir (docs by default, unless set otherwise in mkdocs.yml) (nor a child of site_dir, I guess). Jinja template inheritance makes it easy to over-ride small parts rather than write the entire thing from scratch.

Material theme allows customization & extension . It provides a list of blocks that can be over-ridden. For example htmltitle can be over-ridden to change the title

theme:
  name: 'material'
  custom_dir: 'TestTheme'
{% extends "base.html" %}

{% block htmltitle %}
  <title>Lorem ipsum dolor sit amet</title>
{% endblock %}

The main content is wrapped by content block, sadly this is inside the <main> and <article> tags, and so cannot use it to change those tags (and similar is true of mkdocs base themes). This pattern is followed for all similar blocks.

Local theme based on material

It is possible to develop a full theme for local use. Developing the entire theme from scratch still doesn't seem attractive, but can build new theme based on material using the same blocks in different html tags. First look at how to create a new MkDocs theme.

MkDocs user guide on custom themes

In the directory that has the MkDocs content, i.e. Projects/mdmkdocs/test

$ mkdir TestTheme
$ cd TestTheme
$ touch main.html

Minimal theme

The minimal theme for MkDocs is

<!DOCTYPE html>
<html>
  <head>
    <title>{% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}</title>
  </head>
  <body>
    {{ page.content }}
  </body>
</html>
theme:
    name: null
    custom_dir: 'TestTheme/'

Note

It seems like a chage to the theme files alone isn't enough to trigger their reloading and use when viewing via mkdocs serve; removing and then re-adding the theme: block in mkdocs.yml gives the required nudge.

Development environment

The material documentation on how to use it as a basis for extended themes is good.

First need to set up the development environment and get copy of the material theme. Pre-requisites are node.js (npm) (in project directory, /Projects/mdmkdocs/ not MkDocs directory)

$ sudo apt install npm
$ git clone https://github.com/squidfunk/mkdocs-material
$ cd mkdocs-material
$ pip install -r requirements.txt
$ npm install
$ npm run watch

The last command starts development server on localhost:8000 (don't do this with mkdocs serve running using same port! hint find a free port and use that: mkdocs serve --dev-addr=127.0.0.1:8008 &)

Takes a little while, but then serves the Material documentation using mkdocs and material theme.

Changing the material theme blocks

Can now edit files in mkdocs-material/src/ e.g. in mkdocs-material/src/main.html add json-ld as first element in <body> also add id="main" to <main>.

{% block ocx_pagemetadata %}
    <script type="application/ld+json">
      { "@context": [ "http://schema.org",
         { "oer": "http://oerschema.org/",
           "ocx": "https://github.com/K12OCX/k12ocx-specs/",
           "@base": "https://example.org/ocx/example.html"
         }
       ],
       "@type": ["WebPage"],
       "@id": "#"
       "mainEntity": {"@id": "#main"}
      }
    </script>
{% endblock ocx_pagemetadata %}
...
<!-- Main container -->
      <main class="md-main" id="main">

Taking values from mkdocs.yml

Cannot create new root-level key in mkdocs.yml, but can use either theme or extra

extra:
# OCX metadata
  ocxmd:
    base: http://example.org/test/

at top of base.html

{% set ocxmd = config.extra.ocxmd %}

and in body

{% block ocx_pagemetadata %}
    {% if ocxmd.base %}
      {% set ocx_base = ocxmd.base %}
      <script type="application/ld+json">
        { "@context": [ "http://schema.org",
           { "oer": "http://oerschema.org/",
             "ocx": "https://github.com/K12OCX/k12ocx-specs/",
             "@base": "{{ ocx_base }}"
           }
         ],
         "@type": ["WebPage"],
         "@id": "#",
         "mainEntity": {"@id": "#main"}
        }
      </script>
    {% else %}
      <script type="application/ld+json">
        { "@context": [ "http://schema.org",
           { "oer": "http://oerschema.org/",
             "ocx": "https://github.com/K12OCX/k12ocx-specs/"
           }
         ],
         "@type": ["WebPage"],
         "mainEntity": {"@id": "#main"}
        }
      </script>
    {% endif %}
  {% endblock ocx_pagemetadata %}

Installing modified material theme on test site

Once happy with edits:

$ npm run build
$ cp -r material ../test/material

The theme is located in the material/ directory of the mkdocs-material development directory, and can be moved to the main mkdocs directory and theme_dir set to point to it as a local theme.

theme:
  name: null
  custom_dir: 'material'
  feature:
    tabs: true  
extra:
  # OCX metadata
  ocxmd:
    base: http://example.org/test/
(it crashed unless I added the feature key, dunno why)

Installing as a mkdocs theme

Better still, package the theme and install as mkdocs theme. This will leave the local custom_dir available to modifications of any block without going through the development and building of the whole theme. But for this need it not to clash with the existing material theme. So rename or copy the material directory in the mkdocs-material development directory as materialocx, and edit MANIFEST.in to change occurances of material to materialocx. Material comes with a setup.py, which uses package.json to store many options. Let's not use this, use the outline setup.py from mkdocs .

from setuptools import setup, find_packages

# Load list of dependencies
with open("requirements.txt") as data:
    install_requires = [
        line for line in data.read().split("\n")
            if line and not line.startswith("#")
    ]

# Package description
VERSION = '0.0.1'

setup(
    name="mkdocs-materialocx",
    version=VERSION,
    url='',
    license='',
    description='',
    author='',
    author_email='',
    packages=find_packages(),
    include_package_data=True,
    entry_points={
        'mkdocs.themes': [
            'materialocx = materialocx',
        ]
    },
    zip_safe=False
)

And run (venv)$ python setup.py develop (in a venv!)

Can now use this in mkdocs.yaml

theme:
  name: 'materialocx'
  custom_dir: 'TestTheme'
extra:
  # OCX metadata
  ocxmd:
    base: http://example.org/test/

Can use TestTheme directory to override blocks in materialocx

To Be Continued: - what is the site-wide metadata that should be added? - can the theme be made to handle inpage and between page navigation mark-up?