Skip to content

Content Notes in MkDocs-Material

As you might have noticed if you're following my Fedi posts for any amount of time: I'm a big proponent of content notes (also known as content warnings). They allow you to decide whether you're okay with reading about a particular topic at this moment or not, and they're an essential accessibility tool for people who have had traumatic experiences in the past.

And therefore, I needed them in my blog, too.

This site is built using Material for MkDocs (also known as MkDocs-Material), a collection of designs and plugins and presets and functionality for MkDocs, a Python-based static site generator.

Now, MkDocs-Material comes with support for admonitions, also known as call-outs. They're basically colorful information boxes allowing you to add a side note to your text. For example, the following is an admonition:

Screenshot of box with a rounded light blue border. Its top half has a pale blue background and a light blue "information" icon to the left, followed by the text "Info". The lower half displays the sentence "As explained in the previous paragraph, I'm an admonition."

There are a lot of different types with different colors and icons. I've decided to go with the blue "Info" one, because I think the orange "Warning" one is way too obnoxious and scary. And I've also removed the title to make it more compact.

Using a macro to simplify writing them

Since I expect to use content notes quite regularly, I want it to be as easy as possible to write them. An admonition like the following is pretty verbose to write:

!!! info ""

    ❤️‍🩹 **Content Note:** The following text mentions **thing A**, **thing B**, and **thing C**.

Instead, using the Mkdocs-Macros plugin, I've reduced this down to

{{ cn("thing A", "thing B", "thing C") }}

The full macro definition looks like this:

main.py
def andjoin(
    strings: list[str], *, delim: str = ", ", final: str = " and ", oxford: bool = True
) -> str:
    """
    Join several strings together with a "normal" and an "end" delimiter.
    """
    if len(strings) < 2:
        return delim.join(strings)
    return (
        delim.join(strings[:-1])
        + (delim if oxford and len(strings) > 2 else "")
        + final
        + strings[-1]
    )


def define_env(env):
    @env.macro
    def cn(*topics: str, full: str | None = None) -> str:
        note = full or (
            "The following text mentions "
            + andjoin(list(f"**{topic}**" for topic in topics))
            + "."
        )
        return f'!!! info ""\n\n\t❤️‍🩹 **Content Note:** {note}'

Note that it has a full option that I can use to replace the whole The following text mentions thing with custom text, e.g.

{{ cn(full="These images show furry porn.") }}

Hiding them in excerpts

I'm using excerpt markers to not have the whole post text on listing pages like the blog index or category listings. Since the content note usually comes "before the fold", i.e. before the excerpt marker, it would be included on these pages.

There's nothing inherently wrong with that, but I found the design to be too distracting there. Luckily, pages that contain post listings can be identified by a special CSS class wrapping the posts. I could just add the following to my additional CSS file to hide the content notes:

/* Hide admonitions on pages that only list excerpts (e.g. blog archive). I
 * haven't yet found a way to make them less distracting. */
.md-post--excerpt .admonition {
    display: none;
}

Note that this hides all admonitions, not just content notes, but I'm okay with that.