Diagrams, Math and Icons

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Zensical 0.0.72 (the latest release on PyPI, published on 2026-10-23) and checked on 2026-10-26. It covers the four things that make a documentation page more than text and code: diagrams written as text, mathematical formulas, the icons and emojis that can be placed inline, and the logo and favicon of the site. Every configuration shown was built with Zensical 0.0.72, and the rendering of Mermaid, MathJax, KaTeX, emojis and icons was checked in a browser against a locally served build.

Where this fits

Zensical converts Markdown with the Python Markdown library and its extensions. Diagrams, math and icons are not special features of the page format: each one is an extension (or a small piece of JavaScript) that the configuration enables. The extension turns a piece of Markdown into plain HTML with a recognisable class, and a script that runs in the browser finishes the job.

flowchart LR A["Markdown source"] --> B["Python Markdown extensions"] B --> C["Static HTML with marker classes"] C --> D["Browser scripts"] D --> E["Diagram, formula or icon on screen"]

Figure: extensions rewrite the Markdown at build time and browser scripts draw the result at view time.

The consequence is that every feature on this page has two halves, and a feature that looks broken is almost always missing one of them. The configuration must register the extension (build time), and the page must load the script that draws the result (view time). Mermaid ships its loader inside the Zensical theme, whereas MathJax and KaTeX must be added by the author.

The default extension set and the replacement rule

A site that does not mention markdown_extensions at all gets a default set of extensions. The zensical.toml written by zensical new lists the same set explicitly, and it already contains the three entries this page is about: pymdownx.superfences with a mermaid custom fence, pymdownx.arithmatex in generic mode and pymdownx.emoji with the bundled Twemoji index and SVG generator.

The detail that matters is that the table [project.markdown_extensions] replaces the default set instead of adding to it. A minimal configuration was built to see what happens.

[project]
site_name = "Only attr_list"

[project.markdown_extensions]
attr_list = {}

With only attr_list configured, a fenced mermaid block was rendered as an ordinary code block, \( ... \) stayed in the page as literal text and the shortcode :smile: was printed as written. The build reported no issues. Whoever adds one extension to a site therefore has to copy the whole default list from the file that zensical new generates, then add the new entry. This is the single most common reason for diagrams or formulas that silently stop working after a configuration edit. The extension set is explained further in Markdown Authoring, and the configuration file as a whole in Configuration.

Mermaid diagrams

Mermaid draws diagrams from a text description. Zensical integrates it through SuperFences, the pymdownx extension that handles fenced code blocks: a custom fence named mermaid turns the block into an element that Mermaid draws, instead of a highlighted listing.

Enabling Mermaid

The fence is already part of the default set. When the extension table is customised, it has to be written again:

[project.markdown_extensions]
pymdownx.superfences.custom_fences = [
  { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" },
]

The equivalent mkdocs.yml, which Zensical also reads, uses a Python object reference for the format:

markdown_extensions:
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format

No script has to be added to the site. The JavaScript bundle of the theme refers to Mermaid 11 on the unpkg content delivery network, and the diagram was drawn in the browser without any further configuration. An offline build that is viewed without internet access therefore shows no diagrams, because the Mermaid library itself is not copied into the site. The official page lists three advantages over a hand-made integration: it works with instant navigation, diagrams follow the fonts and colours of the site configuration, and both the light and the dark colour scheme are supported.

Writing a diagram

A diagram is a fenced block whose language is mermaid. The Pocket Units example, the small unit conversion library used throughout this section, can describe the path a value takes when a length is converted:

```mermaid
flowchart LR
  A["Meters"] --> B["Factor 0.3048"] --> C["Feet"]
```

The block was built with Zensical 0.0.72 and the page was opened in a browser. The generated HTML contains a pre element with the class mermaid, and after the page loaded the diagram appeared as a drawn flowchart with three boxes and two arrows. Mermaid places its output in a shadow root, so a script that queries the page for svg elements does not find it. That is expected and is not an error.

The official page demonstrates five diagram types: flowcharts, sequence diagrams, state diagrams, class diagrams and entity-relationship diagrams. These are the types for which the theme adjusts fonts and colours. Pie charts, Gantt charts, user journeys, Git graphs and requirement diagrams still work as Mermaid documents them, but the documentation does not treat them as officially supported, mostly because they do not work well on small screens, and the theme does not restyle them.

A sequence diagram of the same library, as it would be written in a page:

```mermaid
sequenceDiagram
  participant User
  participant Converter
  User->>Converter: convert(3, "m", "ft")
  Converter-->>User: 9.84
```

This sequence diagram was not built; it relies on the same fence as the flowchart above.

Customising Mermaid

Mermaid can be configured by loading a script as the first step. The official page uses this to add ELK layouts. The script is a JavaScript module placed in the docs folder, and it has to expose the Mermaid object as window.mermaid so that Zensical can find it:

import mermaid from 'https://unpkg.com/mermaid@11/dist/mermaid.esm.min.mjs';
import elkLayouts from 'https://unpkg.com/@mermaid-js/layout-elk@0.2/dist/mermaid-layout-elk.esm.min.mjs';

mermaid.registerLayoutLoaders(elkLayouts);
mermaid.initialize({
  startOnLoad: false,
  securityLevel: "loose",
  layout: "elk",
});

// Necessary to make it visible to Zensical
window.mermaid = mermaid;

The module is registered in the configuration. The file is saved as docs/javascripts/mermaid.mjs:

[project]
extra_javascript = [
  "javascripts/mermaid.mjs",
]

The ELK customisation was not executed here. Customising scripts and style sheets in general is the subject of Theming and Customization.

Math with arithmatex

Mathematical notation needs two pieces. The Python Markdown extension pymdownx.arithmatex finds formulas in the Markdown and wraps them in HTML, and a JavaScript library (MathJax or KaTeX) renders the wrapped formulas in the browser. Zensical does not load either library by itself. The arithmatex extension is in the default set, but it is the author who adds the library, and the build with the default configuration confirmed that no math script is included.

The generic mode

Both libraries use pymdownx.arithmatex in generic mode, which is the setting generic = true. In generic mode the extension does not convert the formula itself. It only wraps the formula in a span or div with the class arithmatex, keeping the delimiters, so that the browser library does the work. The build produced these elements for the two kinds of delimiters:

<p>Meters to feet: <span class="arithmatex">\( f = m / 0.3048 \)</span>.</p>
<div class="arithmatex">\[ m = f \times 0.3048 \]</div>

Two input syntaxes are supported in the Markdown, and both end up as the output above. Blocks are written on their own lines between $$ and $$, or between \[ and \]. Inline formulas are written between single dollar signs, or between \( and \). The build shows that the dollar syntax is rewritten to the backslash syntax, which means a library only has to understand the backslash delimiters.

MathJax

MathJax is the more complete library: it reads LaTeX, MathML and AsciiMath and can produce HTML, SVG or MathML output. The configuration adds a small settings script and the library itself from a content delivery network:

[project]
extra_javascript = [
  "javascripts/mathjax.js",
  "https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js",
]

[project.markdown_extensions]
pymdownx.arithmatex.generic = true

The settings script is docs/javascripts/mathjax.js. It limits typesetting to elements with the class arithmatex and re-runs MathJax every time instant navigation loads a page. document$ and component$ are observables that the Zensical theme provides to custom scripts:

window.MathJax = {
  tex: {
    inlineMath: [["\\(", "\\)"]],
    displayMath: [["\\[", "\\]"]],
    processEscapes: true,
    processEnvironments: true
  },
  options: {
    ignoreHtmlClass: ".*|",
    processHtmlClass: "arithmatex"
  }
};

document$.subscribe(() => {
  MathJax.startup.output.clearCache()
  MathJax.typesetClear()
  MathJax.texReset()
  MathJax.typesetPromise()
})

component$.subscribe(({ ref }) => {
  if (ref.classList.contains("md-annotation"))
    MathJax.typesetPromise([ref])
})

A page with an inline formula, a bracket block and a dollar block was built with this configuration and opened in a browser. MathJax replaced the formulas with typeset containers, and the formulas were drawn as expected. Without the document$.subscribe call, formulas on pages reached through instant navigation would not be typeset, because the page is not reloaded.

KaTeX

KaTeX is smaller and faster, supports a subset of LaTeX and outputs HTML. It needs a style sheet in addition to its two scripts, and the settings script uses the renderMathInElement function of its auto-render contrib:

[project]
extra_javascript = [
  "javascripts/katex.js",
  "https://unpkg.com/katex@0/dist/katex.min.js",
  "https://unpkg.com/katex@0/dist/contrib/auto-render.min.js",
]
extra_css = [
  "https://unpkg.com/katex@0/dist/katex.min.css",
]

[project.markdown_extensions]
pymdownx.arithmatex.generic = true

The script is docs/javascripts/katex.js:

document$.subscribe(({ body }) => {
  renderMathInElement(body, {
    delimiters: [
      { left: "$$", right: "$$", display: true },
      { left: "$", right: "$", display: false },
      { left: "\\(", right: "\\)", display: false },
      { left: "\\[", right: "\\]", display: true }
    ],
  })
})

This configuration was built and the page opened in a browser: KaTeX drew the inline formulas and one display formula (three KaTeX elements, one of them a display block).

Choosing between them

The official page weighs the two libraries on five points, summarised here in the table.

Aspect MathJax KaTeX

Speed

Slower on pages with many formulas

Faster

Syntax

Wider LaTeX coverage, also MathML and AsciiMath

Subset of LaTeX

Output

HTML, SVG or MathML (readable by screen readers)

HTML and SVG

Configuration

Many options for precise control

Fewer options

Old browsers

Broader compatibility

Modern browsers

Only one of the two should be loaded on a site. Both are loaded here from a public content delivery network, so a site viewed offline renders the formulas as plain delimited text unless the library files are copied into the docs folder and referenced with relative paths in extra_javascript and extra_css.

Icons and emojis

Zensical bundles more than 10,000 icons and thousands of emojis that are inserted with a shortcode: a name between two colons. Both are provided by the pymdownx.emoji extension, which is in the default set. The explicit form is:

[project.markdown_extensions]
attr_list = {}
pymdownx.emoji.emoji_generator = "zensical.extensions.emoji.to_svg"
pymdownx.emoji.emoji_index = "zensical.extensions.emoji.twemoji"

attr_list is only needed for the colour and animation techniques described below. The generator and the index are the Zensical-provided replacements for the ones of the Material for MkDocs theme.

Emojis

An emoji is written as its Twemoji shortcode, for example :smile:. The shortcodes can be looked up on Emojipedia. The build showed an important detail: an emoji is rendered as an img element whose src points to the Twemoji SVG files on the jsDelivr content delivery network, not as a file copied into the site. A site viewed offline therefore shows a broken image where the emoji should be, whereas the icons below are inlined into the page.

The bundled icon sets

The installed package contains five icon sets in the .icons folder of the theme templates. The file counts below were taken from the installed 0.0.72 package.

Set SVG files Shortcode prefix and example

Material Design

7,447

material- as in :material-ruler:

Simple Icons

3,466

simple- as in :simple-python:

Font Awesome

2,883

fontawesome-brands-, fontawesome-solid- and fontawesome-regular-

Lucide

2,167

lucide- as in :lucide-ruler:

Octicons

773

octicons- as in :octicons-heart-fill-24:

The shortcode of an icon is its path below the .icons folder with every slash replaced by a hyphen and the file extension removed. The file .icons/fontawesome/brands/github.svg is therefore written :fontawesome-brands-github:. The same path with slashes (fontawesome/brands/github) is used wherever the configuration asks for an icon, for example in the site icon settings below. The Lucide icon in the examples of this page was built and appeared as an inline SVG (an svg element inside a span with the class twemoji), so it takes the colour of the surrounding text.

An icon whose shortcode does not exist is not reported: a build with :lucide-nonexistent: in the page finished with "No issues found" and the text was printed unchanged. A misspelled shortcode is therefore found only by looking at the page.

Colours and animations

With attr_list enabled, a CSS class can be attached to an icon in curly braces right after the shortcode. The official page recommends putting the declarations in a style sheet instead of using inline styles. This example colours the icon of the Pocket Units site:

:lucide-ruler:{ .unit-icon }
.unit-icon {
  color: #1565c0;
}
[project]
extra_css = [
  "stylesheets/extra.css",
]

The class was added to the generated element (class twemoji unit-icon) and the icon was drawn in the chosen colour. An animation is written the same way: define a @keyframes rule and a class that uses it in the style sheet, and attach the class to the shortcode, as the official page does with a pulsing heart on an Octicons icon.

Icons in templates

Icons can also be used when a template is overridden. The theme includes the SVG file and wraps it in a span with the class twemoji, which is how the theme itself draws its icons. The path is relative to the .icons folder:

<span class="twemoji">
  {% include ".icons/fontawesome/brands/youtube.svg" %}
</span>

This snippet was not built. Overriding templates with MiniJinja is described in Theming and Customization.

Adding custom icons

More icons come from a folder of SVG files in an override directory. The directory is the custom_dir of the theme, with a .icons folder that has one subfolder per set. Pocket Units adds a pocket set containing one SVG:

.
|-- overrides/
|   `-- .icons/
|       `-- pocket/
|           `-- ruler.svg
|-- docs/
`-- zensical.toml
[project.theme]
custom_dir = "overrides"

[project.markdown_extensions]
attr_list = {}
pymdownx.emoji.emoji_generator = "zensical.extensions.emoji.to_svg"
pymdownx.emoji.emoji_index = "zensical.extensions.emoji.twemoji"
pymdownx.emoji.options.custom_icons = [
  "overrides/.icons",
]

After the build, :pocket-ruler: in a page produced an inline SVG, and pocket/ruler can be used wherever the configuration expects an icon. The two syntaxes follow the rule above: slashes in the configuration, hyphens and colons in Markdown. Both settings are part of the official recipe: custom_dir points the theme at the override directory, and custom_icons lets the emoji extension find the .icons folder for the Markdown shortcodes.

Logo and favicon

The logo appears in the header and in the navigation drawer. It is either a file in the docs folder (any image type, including PNG and SVG) or one of the bundled icons.

[project.theme]
logo = "images/logo.svg"
favicon = "images/favicon.svg"

To use a bundled icon as the logo, the icon section is used instead, with the slash form of the icon name:

[project.theme.icon]
logo = "lucide/ruler"

Both forms were built. A file logo was emitted as an img element with a path relative to the page (../images/logo.svg on a page one level deep), and the favicon as <link rel="icon" href="../images/favicon.svg">. An icon logo was inlined as an svg element with the class lucide lucide-ruler. The logo links to the home page of the site, which is the site_url. Where that is not wanted, the link target is set with a value in the extra table:

[project.extra]
homepage = "https://example.com"

The build confirmed that both logo links (the header and the navigation drawer) then pointed at that address.

Two failure modes were observed. A logo set to a bundled icon that does not exist stops the build with a Python RuntimeError whose message says that the template for the icon file .icons/lucide/does-not-exist.svg was not found. A logo or favicon set to an image file that is not in the docs folder does not stop the build, and the build printed "No issues found". The favicon link then points at a missing file, which shows up only as a missing icon in the browser tab.

Other site icons

Most icons of the theme can be replaced with the same theme.icon table. The documented names and their purpose are:

Name Purpose

logo

The site logo

menu

Opens the navigation drawer

alternate

Changes the language

search

The search icon

share

Shares the search

close

Resets the search and dismisses announcements

top

The back-to-top button

edit

Edits the current page

view

Views the page source

repo

The repository icon

admonition

The icons of admonitions

tag

The icons of tags

previous

Previous page in the footer, hides the search on mobile

next

Next page in the footer

A configuration that swaps the footer arrows and the repository icon:

[project.theme.icon]
repo = "fontawesome/brands/github"
previous = "lucide/arrow-left"
next = "lucide/arrow-right"

The repo and next entries were built as part of the larger example configuration of this page, and the build reported no issues. The previous entry was not built separately.

Pitfalls and version notes

  • The [project.markdown_extensions] table replaces the default extension set. Copy the default list before adding an entry, otherwise Mermaid, math and emojis may stop working with no error message.

  • Mermaid is loaded by the theme from a content delivery network. MathJax, KaTeX and the Twemoji images also come from a network address, so a site that must work without internet access needs local copies of the libraries and a different emoji source. Bundled icons from the .icons sets are inlined and work offline.

  • MathJax and KaTeX are the author’s responsibility. A configuration with arithmatex but without a library script prints the formulas as plain text with their delimiters.

  • In instant navigation mode the settings scripts must subscribe to document$, as the examples do. A script that runs only once at load time works for the first page and then fails on the following pages.

  • A mistyped icon or emoji shortcode in Markdown is not reported by the build, and a missing icon in the configuration stops it. A missing logo or favicon file is not reported at all.

  • The icon sets, the Twemoji index and the generator functions come from the zensical.extensions.emoji module. The names material, fontawesome, octicons, lucide and simple in .icons correspond to the folders of the installed package, so the exact set of icons is that of the installed version.

  • Zensical is in an alpha series (0.0.x) at the time of writing, and a release line starting with 0.1.0 is announced for 2026-11-05, so the contents of the bundled icon sets and the extension behaviour should be rechecked after an upgrade. See Roadmap and Upgrade Policy.

  • The corresponding features of other tools are described in Docusaurus diagrams and math and in the Antora pages on diagrams and math.