Skip to content

Zensical: Documentation Generator

Tool: Zensical

Zensical is a fast static documentation site generator written in Rust and Python by the creators of Material for MkDocs and mkdocstrings. It configures documentation via typed zensical.toml files, replacing legacy mkdocs.yml setups.

What It Does

Zensical generates static technical documentation sites with sub-second live reloading. It automates API documentation via mkdocstrings[python], client-side MathJax LaTeX rendering, dynamic code execution through markdown-exec, and native Mermaid diagram support.

It also compiles machine-readable site/llms.txt and site/llms-full.txt feeds, allowing AI coding assistants to consume the library's entire API reference surface and guides in a single, high-signal contextual payload.

Why It's in Zenith

Zenith chooses Zensical because it standardizes on TOML configuration (zensical.toml), avoiding YAML sprawl and unifying configuration patterns with pyproject.toml.

With strict validation enabled (zensical build --strict), deployments fail automatically if an internal link, anchor tag, footnote, or cross-reference is broken. Designed for dual human and AI consumption, Zensical pairs modern responsive theming with automated machine-readable feeds.

Integrated Plugins

Zenith enhances Zensical with specialized plugins configured under [project.plugins]:

Plugin Capability Purpose in Zenith
gh-admonitions GitHub Alert Callouts Renders GitHub-flavored alert callouts (> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION]) with styled border accents and icons.
llmstxt AI Context Manifests Compiles structured site/llms.txt and site/llms-full.txt feeds, allowing AI coding assistants to consume repository guides and API signatures in a single contextual payload.
markdown-exec Dynamic Code Execution Executes inline Python, Shell, Tree, and Markdown snippets (python exec="1", sh exec="1") during build, ensuring rendered tool configurations and directory trees reflect live repository files without manual copying.
mkdocstrings[python] Automated API Documentation Extracts and documents public Python functions, classes, type parameters, and protocols directly from source code docstrings via Griffe with NumPy style parsing.

Markdown Extensions Suite

Zenith enables a suite of Python-Markdown and PyMdown Extensions under [project.markdown_extensions] to support rich technical authoring:

Extension Category Extensions Enabled Features
Callouts & Blocks admonition, pymdownx.details Collapsible <details> blocks (??? note "Title"), inline notes, and standard warning callouts.
Code & Diagrams pymdownx.superfences, pymdownx.highlight, pymdownx.inlinehilite Custom fences for interactive Mermaid diagrams, line numbers (anchor_linenums), syntax highlighting, and inline code formatting (#!python).
Math & Symbols pymdownx.arithmatex, pymdownx.emoji, pymdownx.smartsymbols Client-side MathJax LaTeX formula rendering (\( ... \) and $$ ... $$), Twemoji SVG emojis (:octicons-code-24:), and typographic arrows.
Interactive UI pymdownx.tabbed, pymdownx.tasklist, zensical.extensions.preview Multi-language tabbed code switchers (=== "Python"), interactive task checkboxes (- [x]), and instant link hover previews.
Typography & Structure attr_list, def_list, footnotes, abbr, toc HTML attribute attaching ({: .grid .cards }), definition lists, footnotes ([^1]), glossary tooltips, and permalink anchors.
Content Reuse pymdownx.snippets Transclusion of external files and code extracts directly into documentation (--8<-- "path/to/file").

Configuration in Zenith

Configured in zensical.toml at the project root:

# ==============================================================================
# Zensical Configuration (zensical.toml)
#
# Tool: Zensical (https://github.com/zensical/zensical)
# Purpose: Modern, fast static documentation site generator configured with TOML.
# Why it's in Zenith: Provides instant live-reloading documentation with native
# dark/light/system mode switching, Lucide icons, Mermaid diagrams, client-side
# MathJax integration for LaTeX math, markdown-exec for dynamic code execution,
# and mkdocstrings for automated Python API documentation generation.
# ==============================================================================

[project]
    edit_uri         = "edit/main/docs/"
    repo_name        = "{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}"
    repo_url         = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}"
    site_author      = "{{ cookiecutter.author_name }}"
    site_description = "{{ cookiecutter.description }}"
    site_name        = "{{ cookiecutter.project_name }}"
    site_url         = "https://{{ cookiecutter.github_username }}.github.io/{{ cookiecutter.project_slug }}/"

    nav = [
        { "Overview" = "index.md" },
        { "Getting Started" = "getting-started.md" },
        { "Reference" = "reference.md" },
        { "Architecture (ADRs)" = [
            "adr/index.md",
            { "ADR-0001: Record Architecture Decisions" = "adr/0001-record-architecture-decisions.md" },
        ] },
        { "Changelog" = "changelog.md" },
    ]

    extra_css = [
        "stylesheets/palette.css",
        "stylesheets/custom.css",
        {% if cookiecutter.include_cli in [True, "yes", "true", "True", "1"] %}
        "stylesheets/termynal.css",
        {% endif %}
    ]

    extra_javascript = [
        "javascripts/mathjax.js",
        "https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js",
        {% if cookiecutter.include_cli in [True, "yes", "true", "True", "1"] %}
        "javascripts/termynal.js",
        {% endif %}
    ]

    [project.validation]
        invalid_links         = true
        invalid_link_anchors  = true
        shadowed_definitions  = true
        shadowed_footnotes    = true
        unresolved_footnotes  = true
        unresolved_references = true
        unused_definitions    = true
        unused_footnotes      = true

    [project.theme]
        features = [
            "announce.dismiss",
            "content.action.edit",
            "content.action.view",
            "content.code.annotate",
            "content.code.copy",
            "content.code.select",
            "content.footnote.tooltips",
            "content.tabs.link",
            "content.tooltips",
            "navigation.footer",
            "navigation.indexes",
            "navigation.instant",
            "navigation.instant.prefetch",
            "navigation.instant.preview",
            "navigation.path",
            "navigation.top",
            "navigation.tracking",
            "search.highlight",
            "search.suggest",
            "toc.follow",
        ]
        language = "en"
        logo = "assets/python-logo.svg"

        [[project.theme.palette]]
            media       = "(prefers-color-scheme)"
            toggle.icon = "lucide/sun-moon"
            toggle.name = "Switch to light mode"

        [[project.theme.palette]]
            media       = "(prefers-color-scheme: light)"
            scheme      = "default"
            toggle.icon = "lucide/sun"
            toggle.name = "Switch to dark mode"

        [[project.theme.palette]]
            media       = "(prefers-color-scheme: dark)"
            scheme      = "slate"
            toggle.icon = "lucide/moon"
            toggle.name = "Switch to system preference"

    [[project.extra.social]]
        icon = "fontawesome/brands/github"
        link = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}"

    [project.markdown_extensions]
        abbr = {}
        admonition = {}
        attr_list = {}
        def_list = {}
        footnotes = {}
        md_in_html = {}
        pymdownx.arithmatex.generic = true
        pymdownx.betterem = {}
        pymdownx.caret = {}
        pymdownx.details = {}
        pymdownx.emoji.emoji_generator = "zensical.extensions.emoji.to_svg"
        pymdownx.emoji.emoji_index = "zensical.extensions.emoji.twemoji"
        pymdownx.highlight.anchor_linenums = true
        pymdownx.highlight.line_spans = "__span"
        pymdownx.highlight.pygments_lang_class = true
        pymdownx.inlinehilite = {}
        pymdownx.keys = {}
        pymdownx.magiclink = {}
        pymdownx.mark = {}
        pymdownx.smartsymbols = {}
        pymdownx.snippets.check_paths = true
        pymdownx.snippets.dedent_subsections = true
        pymdownx.superfences.custom_fences = [
            { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" },
            { name = "tree", class = "tree", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
            { name = "markdown", class = "markdown", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
            { name = "md", class = "md", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
            { name = "python", class = "python", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
            { name = "bash", class = "bash", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
            { name = "sh", class = "sh", validator = "markdown_exec.validator", format = "markdown_exec.formatter" },
        ]
        pymdownx.tabbed.alternate_style = true
        pymdownx.tabbed.combine_header_slug = true
        pymdownx.tasklist.custom_checkbox = true
        pymdownx.tilde = {}
        toc.permalink = true
        zensical.extensions.preview.targets.include = ["*"]

    [project.plugins.gh-admonitions]

    [project.plugins.llmstxt]
        full_output = "llms-full.txt"

        [project.plugins.llmstxt.sections]
            "Documentation" = [
                "index.md",
                "getting-started.md",
                "reference.md",
                "adr/index.md",
                "adr/0001-record-architecture-decisions.md",
                "changelog.md",
            ]

    [project.plugins.markdown-exec]

    [project.plugins.mkdocstrings]
        default_handler = "python"

    [project.plugins.mkdocstrings.handlers.python]
        inventories = ["https://docs.python.org/3/objects.inv"]
        paths = ["src"]
        options.annotations_path = "brief"
        options.backlinks = "tree"
        options.docstring_options.ignore_init_summary = true
        options.docstring_style = "numpy"
        options.extensions = ["griffe_warnings_deprecated"]
        options.line_length = 88
        options.members_order = "source"
        options.merge_init_into_class = true
        options.parameter_headings = true
        options.relative_crossrefs = true
        options.scoped_crossrefs = true
        options.separate_signature = true
        options.show_bases = true
        options.show_inheritance_diagram = true
        options.show_root_heading = true
        options.show_signature_annotations = true
        options.show_signature_type_parameters = true
        options.show_source = true
        options.show_symbol_type_heading = true
        options.show_symbol_type_toc = true
        options.signature_crossrefs = true
        options.summary = true
        options.type_parameter_headings = true
        options.unwrap_annotated = true

Key Invocations

uv run poe docs-serve                  # Start live-reloading dev server (localhost:8000)
uv run zensical serve -a 0.0.0.0:8080  # Bind to custom host address and port
uv run zensical serve -o               # Open browser preview automatically
uv run poe docs-build                  # Strict production build into site/

Live Reloading Watchdog

zensical serve includes an integrated file watchdog:

  • Instant Millisecond Rebuilds: Monitors docs/, zensical.toml, assets, and stylesheets, updating pages in milliseconds as Markdown files are modified.
  • WebSocket Live Browser Sync: Automatically pushes updates to all open browser tabs without manual page refreshes.
  • Agent Workflow Note: When developers run uv run poe docs-serve in a terminal window, AI agents do not need to execute rebuilds during editing turns because the watchdog reloads pages automatically.

Upstream Documentation