Skip to content

Zensical

Project documentation with Zensical.

Setup

An auto configured environment direnv is highly recommended.

# Recommended.
uv init
uv add --dev zensical
uv run zensical serve

Auto Configured environment

Preferred as entering docs directory will automatically setup virtual environment and activate.

Install direnv and create initial venv.

.envrc

0644 {USER}:{USER}

# Create environment file to automatically setup virtual environment.
# direnv executes .envrc in bash and exports back to current shell.

# Create venv if needed and activate.
uv sync --all-extras && source .venv/bin/activate

.gitignore

0644 {USER}:{USER}

# Ignore user-generate UV environments in repository commits.
.venv/
.python-version
main.py
# Enable auto parsing of environment on entering directory.
direnv allow

Add environment files to repository

  • uv.lock
  • .envrc
  • pyproject.toml

Configure Release Publishing

One-time configuration per repository.

github ➔ Settings ➔ Pages

  • Source: Deploy from branch
  • Branch: gh-pages ➔ / (root)

github ➔ Settings ➔ Environments ➔ github-pages

  • Deployment branches and tags:
    • branch: master
    • tags: *

Create new environment if needed. This seems to be hardcoded in Zensical currently.

.github/workflows/docs.yml

0644 {USER}:{USER}

# Alternative setup: https://zensical.org/docs/publish-your-site
---
name: 'Documentation'
on:
  push:
    tags:
      - '*'  # Trigger workflow on tag creation.
permissions:
  contents: 'read'
  pages: 'write'
  id-token: 'write'
jobs:
  deploy:
    # Only run when push event is tag creation.
    if: startsWith(github.ref, 'refs/tags/')
    environment:
      name: 'github-pages'  # Environment to use (hardcoded).
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: 'ubuntu-latest'
    steps:
      - uses: 'actions/configure-pages@v5'
      - uses: 'actions/checkout@v5'
      - uses: 'actions/setup-python@v5'
        with:
          python-version: '3.x'
      - run: 'pip install zensical'
      - run: 'zensical build --clean'
      - uses: 'actions/upload-pages-artifact@v4'
        with:
          path: 'site'
      - uses: 'actions/deploy-pages@v4'
        id: 'deployment'

Local Live Testing

Changes will automatically be detected and re-rendered for local browser testing.

uv run zensical serve

Publish

Generate and push new Zensical site base on current repository.

All warnings and errors must be resolved before committing.

# Create a tagged commit and push.
git commit
git push && git push --tags

# Automatic deploy when pushed to master with tag: .github/workflow/docs.yml
git push && git push --tags

Material Theme

Use Zensical with material theme using minimal extensions to maintain as close to vanilla markdown as possible. Other Zensical vanilla formats may be used if material-specific options are removed.

Config below provides a drop-in replacement for a standard MKDocs Material configuration.

_extra/init.js

0644 {USER}:{USER}

// Syntax highlighting with highlight.js.
document$.subscribe(() => {
  hljs.highlightAll()
})
// Table sorting.
document$.subscribe(function() {
  var tables = document.querySelectorAll("article table:not([class])")
  tables.forEach(function(table) {
    new Tablesort(table)
  })
})

_extra/widescreen.css

0644 {USER}:{USER}

/* Enable width adjustments to window width. */
.md-grid {
  max-width: initial;
}

zensical.toml

0644 {USER}:{USER}

[project]
# Manually generate nav for explicit heading format/casing.
nav = ["README.md"]

site_name = "Docs: A Collection of Notes"
site_url = "https://github.com/{USER}/{REPO}"
repo_url = "https://github.com/{USER}/{REPO}"
site_author = "{USER}"
copyright = "©2026"
docs_dir = "docs"
site_dir = "site"
use_directory_urls = true
dev_addr = "localhost:8000"

# Material Code Highlighting.
#
# Material uses Python Pygments - highlight.js provides better highlights
# for non-python code.
#
# Disable Python Pygments and use highlight.js. Code highlight theme is set
# with extra_css, not integrated hljs.* options.
#
# Reference:
# * https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown-extensions/#highlight-mkdocsyml
# * https://github.com/squidfunk/mkdocs-material/issues/1004
# * https://github.com/highlightjs/highlight.js/tree/main/src/styles
# * https://highlightjs.org/demo
extra_css = [
  "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/vs2015.min.css",
  "_extra/widescreen.css",
]
extra_javascript = [
  "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js",
  "https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js",
  "_extra/init.js",
]

[project.theme]
variant = "classic"  # "modern" is updated look for zensical.
language = "en"
features = [
  "header.autohide",
  "content.action.edit",
  "content.footnote.tooltips",
  "navigation.instant",
  "navigation.instant.prefetch",
  "navigation.instant.progress",
  "navigation.tracking",
  "navigation.path",
  "navigation.indexes",
  "navigation.top",
  "toc.follow",
  "search.highlight",
]
icon.annotation = "material/chevron-right-circle"
navigation_depth = 5

[[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: dark)"
scheme = "slate"
primary = "black"
accent = "indigo"
toggle.icon = "lucide/sun"
toggle.name = "Switch to light mode"

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

[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/r-pufky"

[[project.extra.social]]
icon = "simple/gnuprivacyguard"
link = "https://keys.openpgp.org/vks/v1/by-fingerprint/466EEC2B67516C7117C85CE3A0BC35D16698BAB9"

[project.markdown_extensions.admonition]
[project.markdown_extensions.attr_list]
[project.markdown_extensions.def_list]
[project.markdown_extensions.footnotes]
[project.markdown_extensions.md_in_html]
[project.markdown_extensions.pymdownx.arithmatex]
generic = true

[project.markdown_extensions.pymdownx.betterem]
[project.markdown_extensions.pymdownx.caret]
[project.markdown_extensions.pymdownx.details]
[project.markdown_extensions.pymdownx.emoji]
emoji_generator = "zensical.extensions.emoji.to_svg"
emoji_index = "zensical.extensions.emoji.twemoji"

[project.markdown_extensions.pymdownx.highlight]
anchor_linenums = true
line_spans = "__span"
use_pygments = false  # Disable for highlight.js.

[project.markdown_extensions.pymdownx.inlinehilite]
[project.markdown_extensions.pymdownx.magiclink]
[project.markdown_extensions.pymdownx.superfences]
[project.markdown_extensions.pymdownx.tabbed]
alternate_style = true
slugify = { object = "pymdownx.slugs.slugify", kwds = { case = "lower" } }

[project.markdown_extensions.pymdownx.tasklist]
custom_checkbox = true

[project.markdown_extensions.pymdownx.tilde]
[project.markdown_extensions.pymdownx.mark]
[project.markdown_extensions.tables]
[project.markdown_extensions.zensical.extensions.glightbox]
auto = true
auto_themed = true
auto_caption = true
caption_position = "bottom"
width = "auto"
height = "auto"

# TODO(zensical): Currently being implemented.
#     plugins:
#       - git-revision-date-localized:
#           enable_creation_date: true
#
#     https://github.com/zensical/backlog/issues/18

# TODO(zensical): Currently being migrated and therefore disabled.
# Use with: zensical build --strict
# [project.validation]
# invalid_links = true
# invalid_link_anchors = true
# unresolved_references = false
# unresolved_footnotes = false
# unused_definitions = false
# unused_footnotes = false
# shadowed_definitions = false
# shadowed_footnotes = false

Site will now render with Material theme using local live testing and publishing.

Reference12