Skip to content

MkDocs Plugins

I wrote a collection of plugins for (Material for) MkDocs for my own personal use. The syntax I designed is pretty dangerous because of how minimal it is, though I haven't found any glaring issues yet. The minimalism is because I am very lazy, and its hard enough to be bothered to write all this crap down, let alone format everything.

Cheatsheet

Styled Text

<<text>style>                    styled text
<<text>style attr=val>           styled with overrides
<<PageName>>                     auto-link to page if exists

Styles: subtitle, center

Overrides: align=left|center|right, color=#hex, size=1.5em, bold, italic, justify

Images & Figures

![alt](image.png)                          basic image
![alt](image.png){width=50%}               sized image
![alt](image.png){align=left width=50%}    floated image
![alt](image.png){caption="text"}          figure with caption
![alt](image.png){align=right caption="text" width=40%}

Attributes: width, height, align (left/center/right), caption

Galleries

![](directory/)                            gallery from folder
![](directory/){cols=4}                    4-column gallery
![](dir1/|dir2/|dir3/)                     multi-directory gallery (order preserved)
![](directory/){orderby=date}              sort by date instead of name

Videos

![](video.mp4)                             basic video
![alt](video.mp4){width=80% autoplay loop muted}

Attributes: width, height, controls (default true), autoplay, loop, muted, preload

Formats: mp4, webm, mov, avi, mkv

3D Models

![](model.stl)                             basic 3D viewer
![](model.stl){width=100% height=600px color=ffffff}

Formats: stl, dxf

Redaction

**REDACTED**

PlantUML Diagrams

\`\`\`puml
@startuml
Alice -> Bob: Hello
Bob --> Alice: Hi
@enduml
\`\`\`

Columnizer Columns

If there is a blank line, or any line which is not a new column start '{', directly after a column close '}' then those two columns are not bound together.

Basic two columns:

{
first column content
}
{
second column content
}

With properties (goes after closing brace):

{
wide column
}[width=66%]
{
narrow column
}

Nested columns:

{
outer left
}
{
  {
  inner left
  }
  {
  inner right
  }
}

Braces in content are handled (inline code, Jinja2, attribute lists):

{
Here's some code: `{foo: bar}`
And a Jinja variable: {{ something }}
With an attribute {: .myclass}
}
{
Second column
}

Properties: width=66%

Standard Markdown

Headers:

# H1  ## H2  ### H3  #### H4

Text formatting:

**bold**  *italic*  `code`

Lists:

* bullet list
    * nested item

1. numbered list
2. second item

Links:

[link text](url)
[internal link](./page.md)
<https://auto-link.com>

Blockquote:

> blockquote

Code block:

\`\`\`language
code block
\`\`\`

HTML Helpers

Collapsible section:

<details markdown="1">
<summary>Click to expand</summary>
Hidden content here (markdown works)
</details>

Clear floats (force content below floated images):

<div style="clear: both;"></div>

Prevent header from clearing floats:

#### My Header {: .no-clear}

Plugin Documentation

Displaymotron Core

Displaymotron intercepts markdown image syntax ![alt](path){attrs} and renders different content based on path type:

Path Type Handler
Directory Gallery module
.mp4, .webm, etc. Video module
.stl, .dxf Model3D module
Image + figure attrs Figure module
Text file Textfile module
Binary file Textfile (hex preview)

Centralized Extensions (extensions.py)

IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp"}
VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".avi", ".mkv"}
MODEL_EXTENSIONS = {".stl", ".dxf"}

Plugin Lifecycle

on_config        → Register JS files
on_page_markdown → Process all ![](path) patterns
on_post_page     → Inject CSS for used features
on_post_build    → Copy JS files to site

Page Meta Flags

Each feature sets a flag in page.meta to enable conditional CSS injection:

  • displaymotron_video
  • displaymotron_3d
  • displaymotron_text
  • displaymotron_gallery
  • displaymotron_figure
  • displaymotron_columnizer

Renders directory contents as masonry image gallery using CSS columns.

Syntax

![alt text](path/to/directory/){cols=3 orderby=name}

Attributes

Attribute Description
cols=N Number of columns (default: 3)
width=N% Column width percentage (alternative to cols)
orderby=date\|name Sort order (default: name with natural sort)

Flow Diagram

@startuml
start
:Parse attributes (cols, orderby, width);
:Scan directory for images/videos;
:Sort files (natural or date);
:Get image dimensions for aspect ratios;
:Determine container width;
note right
  From columnizer data-width-em
  or default 70em
end note
:Compute column reductions;
:Generate CSS with container queries;
:Build HTML gallery div;
stop
@enduml

Responsive Column Reduction

Galleries reduce columns as container narrows. Breakpoints use cascading ratios relative to container width:

starting_width = container_width_em (or 70em default)
reduction_ratio = 0.6

For 3-col gallery at 70em:
  3→2 at 70 × 0.6 = 42em
  2→1 at 42 × 0.6 = 25.2em

Each breakpoint is relative to the previous, creating proportional spacing.

Container Query Integration

Galleries detect their container via data-width-em attribute on columnizer columns:

@container displaymotron-col (max-width: 42.0em) {
  #gallery-abc123 { column-count: 2; }
}

For galleries outside columns, uses @container content instead.


Columnizer Module

Creates multi-column layouts using curly brace syntax.

Syntax

{
First column content
}[width=66%]
{
Second column content
}
{
Third column content
}

Width Calculation

  1. Explicit widths sum up
  2. Remaining percentage divided among auto columns
  3. Each column gets data-width-em attribute: 70 × width_pct / 100

Flow Diagram

@startuml
start
:Parse markdown line by line;
:Detect opening brace {;
:Track brace depth;
:Accumulate block content;
:On depth=0, extract content;
:Parse [width=X%] arguments;
:Continue until chain breaks;
note right
  Chain breaks on:
  - Blank line
  - Non-brace content
  - Less than 2 blocks
end note
:Calculate column widths;
:Generate HTML with flex layout;
:Add container-type: inline-size;
stop
@enduml

Generated HTML

<div class="displaymotron-columns" markdown="1">
  <div class="displaymotron-column" data-width-em="46.2" 
       style="flex: 0 0 calc(66% - 0.5em);" markdown="1">
    Content...
  </div>
  <div class="displaymotron-column" data-width-em="23.8" 
       style="flex: 0 0 calc(34% - 0.5em);" markdown="1">
    Content...
  </div>
</div>

CSS Container Setup

.displaymotron-column {
  min-width: 0;
  container-type: inline-size;
  container-name: displaymotron-col;
}

Figure Module

Renders images with captions, supporting rows, columns, and inline floating.

Syntax

![alt](image.jpg){caption="My caption" align=left width=300}

Attributes

Attribute Values Description
caption string Caption text
caption-width CSS value Max caption width
caption-align left/center/right Caption text alignment
align left/center/right Figure alignment/float
gridType row/col Layout direction for chains
normalizeHeights true/false Equal heights in row
normalizeWidths true/false Equal widths in column
aspectRatio fixed/stretch/zoom Image fit mode
rowHeight CSS value Fixed row height
colHeight CSS value Fixed column height

Figure Chains

Consecutive figures on same line form a chain:

![](a.jpg){gridType=row} ![](b.jpg) ![](c.jpg)

Flow Diagram

@startuml
start
:Parse figure attributes;
:Extract FigureConfig;
if (Multiple figures on line?) then (yes)
  :Build figure chain;
  :Apply hierarchical inheritance;
  :Group by gridType boundaries;
  if (gridType=row?) then (yes)
    :Calculate aspect ratios;
    :Set flex-grow per image;
    :Render horizontal row;
  else (col)
    :Calculate inverse ratios;
    :Set flex-grow per image;
    :Render vertical column;
  endif
else (single)
  if (Inline float?) then (yes)
    :Render with float: left/right;
  else (no)
    :Render centered figure;
  endif
endif
stop
@enduml

Inline Floating

Figures without blank line before them float alongside text:

Some text here.
![](image.jpg){caption="Float right" align=right width=200}
More text wraps around the image.

Height/Width Normalization

Row normalization uses aspect ratios for flex-grow:

## flex-grow = width / height (aspect ratio)
aspect_ratios = [img_width / img_height for img in images]

Column normalization uses inverse:

## flex-grow = height / width (inverse aspect ratio)
inverse_ratios = [img_height / img_width for img in images]

NarrowNav Plugin

Auto-calculates minimum navigation sidebar width based on nav item titles.

Configuration

plugins:
  - narrownav:
      char_width_em: 0.6      # Character width estimate
      indent_per_level_em: 1.2 # Indent per nesting level
      base_padding_em: 4.5    # Base padding (scrollbar, arrows)
      min_width_em: 8.0       # Minimum width
      max_width_em: 20.0      # Maximum width

Flow Diagram

@startuml
start
:on_nav hook receives Navigation object;
:Walk nav.items recursively;
:For each item:;
:  width = len(title) × char_width_em;
:  width += depth × indent_per_level_em;
:  width += base_padding_em;
:  track max_width;
:Clamp to min/max bounds;
:on_post_build writes CSS file;
:on_post_page injects CSS link;
stop
@enduml

Width Calculation

total_width = (len(title) * char_width_em) + 
              (depth * indent_per_level_em) + 
              base_padding_em

Generated CSS

/* Auto-generated by NarrowNav plugin */
.md-sidebar--primary {
    width: 14.2em;
}
.md-sidebar--primary .md-sidebar__scrollwrap {
    width: 14.2em;
}

When gallery is inside a columnizer column:

@startuml
participant Columnizer
participant Plugin
participant Gallery

Columnizer -> Plugin: Process {blocks}
Plugin -> Plugin: Calculate column widths
Plugin -> Plugin: Add data-width-em to HTML
Plugin -> Gallery: render(..., container_width_em=35.0)
Gallery -> Gallery: Compute breakpoints from 35em
Gallery -> Gallery: Generate @container displaymotron-col queries
@enduml

The data-width-em attribute bridges columnizer width to gallery breakpoint calculation, enabling proper responsive behavior when galleries are nested inside columns.

StyledText Parsing

_parse / _find_styled_block_parse_find_styled_block_find_closerStyledTextNode_is_escaped_is_valid_stylecode_blockfound triple-backtickskip to closing triple-backtickfound <<true, add textfalsefound << orcontinuefoundcheckbetween >false, continuetruedepth == 0recurse
_parse / _find_styled_block_parse_find_styled_block_find_closerStyledTextNode_is_escaped_is_valid_stylecode_blockfound triple-backtickskip to closing triple-backtickfound <<true, add textfalsefound << orcontinuefoundcheckbetween >false, continuetruedepth == 0recurse

StyledText Rendering

render / _apply_stylerender_unescape_apply_style_inject_style_find_page_url_parse_attrs_build_override_stylestring segmentnode segment, recursedoneempty styleknown stylelink or span
render / _apply_stylerender_unescape_apply_style_inject_style_find_page_url_parse_attrs_build_override_stylestring segmentnode segment, recursedoneempty styleknown stylelink or span