Skip to content
New to Sumi? Read the introduction.
Sumi

Sumi

A modern, local-first documentation theme for MkDocs. Markdown in, beautiful docs out.

November 2, 2026

Sumi documentation#

Sumi is a documentation theme for MkDocs. It turns a folder of plain Markdown into a complete, searchable, printable website — with a home page, a blog and release notes — without Node.js, a CDN or a hosted service.

  • Install Add Sumi to an existing MkDocs project in two lines of configuration.
  • Write Sixteen components, all written in native Markdown.
  • Pages Landing pages, a blog, release downloads and a print edition.
  • Customize Colours, navigation, search, languages, versions and template overrides.

What you get#

Everything listed here ships with the theme. Nothing is fetched from the network at build or read time.
Area Included
Reading Top tabs, collapsible sidebar, breadcrumbs, table of contents with scroll spy, previous / next
Discovery Local full-text search with CJK support, merged with a command palette (⌘ K)
Authoring Callouts, tabs, steps, cards, fields, captions, galleries, badges, file trees, Mermaid, ECharts, mind maps, KaTeX, keys, 1 900+ icons
Pages Data-driven landing page, blog with tags and Atom feed, releases with checksums, whole-site print edition
Outputs HTML, print, a Markdown copy of every page, llms.txt, Atom
Operations Dark mode, mike versions, language switcher, edit links, Git "last updated" dates, analytics

Principles#

Sumi follows four rules. They are explained in Design principles.

  1. Content stays Markdown. A component is a blockquote, a list, a table or a fence with one attribute line.
  2. Everything is local. Every font, icon and script is part of your built site.
  3. Pay only for what you use. Diagrams and formulas load their runtime on the pages that contain them.
  4. People and agents are both readers. Every page is published as HTML and as Markdown.

Install#

Sumi is a Python package. It depends on MkDocs NG, so installing Sumi installs MkDocs too.

Requirements#

  • Python 3.10 or newer
  • An MkDocs project, or an empty folder

Install the package#

pip
pip install mkdocs-sumi
uv
uv add --dev mkdocs-sumi
pipx
pipx install mkdocs-ng --include-deps
pipx inject mkdocs-ng mkdocs-sumi

Enable the theme and the plugin#

Sumi has two halves: the theme draws the page and the plugin adds the Markdown components, the blog and the extra outputs. Enable both:

mkdocs.yml
theme:
  name: sumi

plugins:
  - search    # powers ⌘K search
  - llms_txt  # optional: llms.txt and a Markdown copy of every page
  - sumi

Important

When you add a plugins: list, MkDocs no longer enables search for you. List it explicitly, as above.

Preview#

mkdocs serve

Open http://127.0.0.1:8000/. Press ⌘ K (or Ctrl K) to search.

Next#

Continue with Your first site, or jump to Writing.

Your first site#

This page builds a small site from scratch: a home page, two guide pages and a blog post.

  1. Create the project

    mkdocs new my-docs
    cd my-docs
    
  2. Lay out the files

    • my-docs/
      • docs/
        • index.mdHome page
        • guide/
          • index.mdSection landing, linked from the sidebar title
          • install.md
        • blog/
          • posts/
            • hello.mdAny page under blog/ is a post
      • mkdocs.ymlSite configuration
  3. Configure the site

    mkdocs.yml
    site_name: My Docs
    site_url: https://example.com/
    repo_url: https://github.com/me/my-docs
    
    theme:
      name: sumi
    
    plugins:
      - search
      - sumi
    
  4. Preview and build

    mkdocs serve        # live preview
    mkdocs build        # writes static files to site/
    

How navigation is built#

Top-level sections become tabs in the header. Pages inside the active tab appear in the sidebar. A section whose first page is index.md uses that page as its landing: the section title links to it.

Tip

Without a nav: key, MkDocs builds the navigation from your folders. Blog posts are kept out of the sidebar automatically.

Deploy#

mkdocs build writes plain HTML, CSS and JavaScript to site/. Upload that folder to any static host — GitHub Pages, Cloudflare Pages, Netlify, an S3 bucket or an Nginx server. Nothing needs to run after deployment.

mkdocs gh-deploy      # build and push to the gh-pages branch

Migrate from Material#

Sumi reads the content and most of the configuration written for Material for MkDocs. Switching is usually a two-line change.

mkdocs.yml
 theme:
-  name: material
+  name: sumi
 plugins:
   - search
+  - sumi

What keeps working#

You use In Sumi
admonition, !!! note and ??? note Styled as callouts, same colours as > [!NOTE]
pymdownx.details, pymdownx.tabbed (alternate style) Styled natively
pymdownx.superfences, pymdownx.highlight Supported; Sumi then leaves highlighting to them
pymdownx.arithmatex Rendered with the bundled KaTeX
pymdownx.keys Kept; Sumi's own ++Ctrl+K++ steps aside
extra.social, extra.alternate, extra.analytics Read as-is, including fontawesome/brands/… icon names
mkdocstrings API pages Styled: signatures, symbol badges (show_symbol_type_heading), labels, nested members, backlinks and source panels. Recommended options
Plugin CSS that uses Material's --md-* colours or [data-md-color-scheme] Follows Sumi's palette and dark mode. Authors: make your plugin work with Sumi
mike versions theme.version_provider: mike
Blog: blog/posts/, .authors.yml, date, authors, tags, <!-- more --> Read as-is
hide: [toc, navigation] front matter hide: [toc, sidebar]

What changes#

  • Material's theme.features and theme.palette lists are ignored. Use theme options instead.
  • Icons written as :material-…: or :octicons-…: need the pymdownx.emoji extension as before. Sumi's own inline icons are :lucide-name: and :brand-name:.
  • Material Insiders features are not emulated.

Note

If a page looks wrong after switching, open an issue with the Markdown that causes it.

Writing with Sumi#

Sumi adds sixteen components to Markdown. None of them introduces new syntax that breaks on GitHub: each one is a blockquote, a list, a table, an image or a code fence, sometimes followed by a single attribute line in braces.

1. Install the theme
2. Enable the plugin
{.steps}

If a renderer does not know Sumi, the reader still sees a numbered list. Sumi sees a procedure.

Components#

Component Written as Page
Callout > [!NOTE], > [!TIP]- Callouts
Code block with title ```py title="app.py" Code blocks
Tabs ```sh {tab="Linux"} Tabs
Steps ordered list + {.steps} Steps
Cards bullet list of links + {.cards} Cards
Fields table + {.fields} Fields
Table options table + {.wide}, {caption="…"} Tables
Figure image + {caption="…"} Images
Gallery list of images + {.gallery} Galleries
File tree ```filetree File trees
Diagram ```mermaid Diagrams
Chart ```echarts (YAML or JSON) Charts
Mind map ```markmap Mind maps
Math $…$, $$…$$ Math
Keys and icons ++Ctrl+K++, :lucide-rocket: Keys and icons
Badge **New**{.badge} Badges

Attribute lines#

An attribute line is a paragraph that contains only {…} and sits directly under the block it modifies. It can carry classes, an id and key–value pairs:

| Name | Type |
| --- | --- |
| `port` | integer |
{.fields #server-options caption="Server options"}

Classes that Sumi does not recognise are kept, so you can style blocks yourself in extra_css.

Markdown extensions#

The sumi plugin enables these Python-Markdown extensions for you, so they need no configuration:

  • attr_list, md_in_html, tables, toc (with permalinks), footnotes, def_list, admonition
  • fenced_code and codehilite — unless pymdownx.superfences and pymdownx.highlight are configured, in which case Sumi leaves code to them

Any other extension in markdown_extensions continues to work.

Tip

Every page of this site was written with these components. Click Copy as Markdown at the top of a page to see its source.

Callouts#

A callout draws attention to information that should not be missed. Sumi uses the syntax GitHub introduced for alerts, so callouts render on GitHub, in your editor's preview and in Sumi alike.

> [!NOTE]
> Sumi reads the same syntax GitHub does.

Note

Sumi reads the same syntax GitHub does.

The five types#

Note

Useful information that users should know, even when skimming.

Tip

Helpful advice for doing things better or more easily.

Important

Key information users need to achieve their goal.

Warning

Urgent information that needs immediate attention to avoid problems.

Caution

Advises about risks or negative outcomes of certain actions.

> [!NOTE]
> [!TIP]
> [!IMPORTANT]
> [!WARNING]
> [!CAUTION]

Sumi also accepts the type names used by Material for MkDocs — info, success, question, failure, danger, bug, example, quote, abstract and todo — each mapped to the closest colour.

Custom titles#

Text after the marker replaces the default title:

> [!WARNING] Back up first
> `mkdocs gh-deploy --force` replaces the whole `gh-pages` branch.

Back up first

mkdocs gh-deploy --force replaces the whole gh-pages branch.

Collapsible callouts#

Add - after the marker for a callout that starts closed, or + for one that starts open but can be closed.

> [!TIP]- Why is it called Sumi?
> *Sumi* (墨) is the ink of East Asian calligraphy: one colour, many shades.
Why is it called Sumi?

Sumi (墨) is the ink of East Asian calligraphy: one colour, many shades.

Collapsed callouts are printed open.

Rich content#

A callout can hold anything a blockquote can — lists, code, tables, even other callouts.

Before you upgrade

1. Read the release notes. 2. Pin the version in requirements.txt:

```text
mkdocs-sumi==0.1.0
```

Admonitions#

If your pages already use the admonition extension, they keep working and look the same:

!!! warning "Deprecated"
    Use `theme.palette` instead.

Deprecated

Use theme.palette instead.

??? note and ???+ note from pymdownx.details are styled as collapsible callouts.

Code blocks#

Fenced code blocks are highlighted at build time by Pygments. Every block gets a copy button; no JavaScript highlighter is shipped.

def greet(name: str) -> str:
    return f"Hello, {name}!"

File names#

Add title="…" after the language to show a file name above the code:

```toml title="pyproject.toml"
[project]
name = "my-docs"
dependencies = ["mkdocs-sumi"]
```
pyproject.toml
[project]
name = "my-docs"
dependencies = ["mkdocs-sumi"]

Line numbers and highlighted lines#

These options come from Python-Markdown's codehilite, or from pymdownx.highlight if you use it:

```python hl_lines="2 3"
def total(items):
    subtotal = sum(i.price for i in items)
    return subtotal * 1.2
```
def total(items):
    subtotal = sum(i.price for i in items)
    return subtotal * 1.2

To show line numbers on every block, configure the extension:

mkdocs.yml
markdown_extensions:
  - codehilite:
      linenums: true

Shell sessions#

Use console for transcripts with prompts and output. The copy button then copies only the commands — without the $ prompts and without the output.

$ mkdocs build
INFO    -  Building documentation to directory: site
INFO    -  Documentation built in 0.42 seconds

Diffs#

 theme:
-  name: mkdocs
+  name: sumi

Inline code#

Write inline code with single backticks: mkdocs serve. In prose, Sumi gives inline code a quiet background that works in light and dark mode.

Tip

To show tabs with one variant per operating system or language, see Tabs.

Tabs#

Consecutive code fences that each carry {tab="…"} become one tab set. Without Sumi, they are simply consecutive code blocks with a hint of what each one is for.

```sh {tab="macOS"}
brew install python
```

```sh {tab="Ubuntu"}
sudo apt install python3
```

```powershell {tab="Windows"}
winget install Python.Python.3.12
```
macOS
brew install python
Ubuntu
sudo apt install python3
Windows
winget install Python.Python.3.12

Synchronised tabs#

Tab sets with the same labels stay in sync: choose Windows once and every matching tab set on every page follows. The choice is remembered in the browser.

macOS
python3 -m venv .venv && source .venv/bin/activate
Ubuntu
python3 -m venv .venv && source .venv/bin/activate
Windows
py -m venv .venv; .venv\Scripts\Activate.ps1

Titles inside tabs#

title and tab combine:

```yaml {tab="YAML" title="mkdocs.yml"}
theme: sumi
```

```toml {tab="TOML" title="pyproject.toml"}
[tool.mkdocs]
theme = "sumi"
```
YAML
mkdocs.yml
theme:
  name: sumi
TOML
pyproject.toml
[tool.mkdocs]
theme = "sumi"

Tabs with prose#

For tabs that contain more than code, use pymdownx.tabbed with alternate_style: true. Sumi styles it to match:

mkdocs.yml
markdown_extensions:
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true

In print and in the Markdown copy, every tab is shown one after another under its label.

Steps#

Put {.steps} under an ordered list to draw it as a procedure: numbered markers joined by a line, with each step's first line as its heading.

1. Create a virtual environment

    ```sh
    python -m venv .venv
    ```

2. Install Sumi

    ```sh
    pip install mkdocs-sumi
    ```

3. Start the preview server

    Run `mkdocs serve` and open the address it prints.
{.steps}
  1. Create a virtual environment

    python -m venv .venv
    
  2. Install Sumi

    pip install mkdocs-sumi
    
  3. Start the preview server

    Run mkdocs serve and open the address it prints.

Short steps#

Steps do not need bodies:

  1. Write
  2. Preview
  3. Publish

Guidelines#

  • Start each step with a verb.
  • Keep one action per step. If a step needs a list of its own, it is probably two steps.
  • Indent step content by four spaces so it stays inside the step.

Cards#

A bullet list of links followed by {.cards} becomes a grid. The link is the card title; the text after it is the description; an inline icon at the start becomes the card icon.

- :lucide-rocket: [Install](../start/install.md)
  Add Sumi to a project.
- :lucide-pen-line: [Write](index.md)
  Components in plain Markdown.
- :lucide-palette: [Customize](../customize/theme.md)
  Colours, fonts and layout.
{.cards}
  • Install Add Sumi to a project.
  • Write Components in plain Markdown.
  • Customize Colours, fonts and layout.

Columns#

Cards fill as many columns as fit, up to three. Set cols to fix the number:

{.cards cols=2}
  • Source code Report a bug or send a pull request.
  • PyPI Release history and downloads.

Items without a link render as static cards — useful for a list of features:

  • Local-first Every asset ships with your site.
  • Fast No framework; one small script.
  • Printable A whole-site print edition.

Fields#

Configuration options and API parameters are usually written as a table, and tables become hard to read once descriptions grow. Add {.fields} and Sumi turns each row into a reference entry: the name and its type, default and required flag on one line, the description underneath.

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `site_name` | string | | yes | The title of the site, used in the header and in every page title. |
| `site_url` | string | `null` | | The canonical address of the built site. Required for the sitemap, the feed and `llms.txt`. |
| `strict` | boolean | `false` | | Stop the build on any warning, such as a broken link. |
{.fields}
site_namestringrequired
The title of the site, used in the header and in every page title.
site_urlstringDefault null
The canonical address of the built site. Required for the sitemap, the feed and llms.txt.
strictbooleanDefault false
Stop the build on any warning, such as a broken link.

Which columns are metadata#

The first column is always the name. Columns headed Type, Default and Required (or 类型, 默认值, 必填) become chips next to it. Every other column becomes part of the description.

To choose the metadata columns yourself, list their headings in meta:

{.fields meta="type since"}
idintegerSince 1.0
Unique identifier of the release.
assetslistSince 1.2
Files attached to the release, with their size and SHA-256 checksum.

Linking to a field#

Each entry keeps the content of its name cell, so you can add an anchor with attr_list:

| `strict`{#strict} | boolean | `false` | Stop on warnings. |

The Markdown copy of the page keeps the original table.

Tables#

Tables are standard GitHub-flavoured Markdown. Sumi gives them a frame, sticky headers and horizontal scrolling on narrow screens.

Command Purpose
mkdocs new Create a new project
mkdocs serve Start a live-reloading preview
mkdocs build Build the site
mkdocs gh-deploy Build and deploy to GitHub Pages

Captions#

| Plan | Price |
| --- | --- |
| Open source | Free |
{caption="Sumi is licensed under Apache 2.0."}
Sumi is licensed under Apache 2.0.
Plan Price
Open source Free

Variants#

Add one or more of these classes on the attribute line:

Class Effect
.wide Let the table use the full width of the content area
.compact Reduce cell padding for dense data
.striped Shade alternate rows
.center Centre all cells
{.compact .striped caption="Variants"}

Alignment#

Column alignment uses the standard colon syntax:

| Left | Centre | Right |
| :--- | :---: | ---: |
| a | b | 1.00 |
Left Centre Right
a b 1.00
longer text c 120.50

Tip

For option and parameter references, use fields instead of a wide table.

Images#

Images use standard Markdown. Paths are relative to the page, so the image works in your editor, on GitHub and on the site.

![The Sumi mark](../../assets/sumi-mark.svg)

Figures#

Add {caption="…"} on the line after an image to turn it into a figure:

![The Sumi mark](../../assets/sumi-mark.svg)
{caption="The Sumi mark: an ink stroke and a vermilion seal."}
The Sumi mark
The Sumi mark: an ink stroke and a vermilion seal.

Zoom#

Click an image in the content to view it full-screen; press Esc or click again to close. Images inside links and cards are not zoomable. To opt an image out, give it the no-zoom class:

![Diagram](flow.png){.no-zoom}

Light and dark variants#

Append #only-light or #only-dark to an image URL to show it in one colour scheme only:

![Architecture](arch-light.png#only-light)
![Architecture](arch-dark.png#only-dark)

Size and placement#

With attr_list, set the width or float an image:

![Logo](logo.png){width="120"}
![Screenshot](shot.png){.left width="320"}

Note

Images are lazy-loaded. Give large images explicit width and height attributes to avoid layout shift.

File trees#

A filetree fence turns a directory listing into a navigable tree with folder and file icons. Paste the output of tree, or write the structure with plain indentation.

```filetree
my-docs/
├── docs/                 # Markdown sources
│   ├── index.md
│   ├── guide/
│   │   ├── install.md
│   │   └── usage.md
│   └── assets/
│       └── logo.svg
├── overrides/            # Template overrides (optional)
│   └── main.html
├── pyproject.toml
└── mkdocs.yml            # Site configuration
```
  • my-docs/
    • docs/Markdown sources
      • index.md
      • guide/
        • install.md
        • usage.md
      • assets/
        • logo.svg
    • overrides/Template overrides (optional)
      • main.html
    • pyproject.toml
    • mkdocs.ymlSite configuration

Rules#

  • A name ending in /, or a name with children, is a folder. Click a folder to fold it.
  • Text after # is a comment and is aligned in a column.
  • File icons follow the extension: code, text, configuration, data, images and archives.
  • Indentation by spaces works as well as box-drawing characters.

Plain indentation#

```filetree title="Package layout"
src/
  mkdocs_sumi/
    plugin.py      # The MkDocs plugin
    theme/         # Jinja templates and assets
tests/
```
Package layout
  • src/
    • mkdocs_sumi/
      • plugin.pyThe MkDocs plugin
      • theme/Jinja templates and assets
  • tests/

The tree is built at build time from plain HTML <details> elements, so it needs no JavaScript and prints fully expanded.

Diagrams#

A mermaid fence is drawn as a diagram by Mermaid. Sumi bundles Mermaid with the theme and loads it only when a diagram is about to scroll into view. Mermaid is split by diagram type, so a page with one flowchart downloads the flowchart code and nothing else. Other pages do not pay for it at all.

```mermaid
flowchart LR
  md[Markdown] --> mk{MkDocs}
  mk --> html[HTML pages]
  mk --> txt[llms.txt]
  mk --> copy[Markdown copies]
```
flowchart LR
  md[Markdown] --> mk{MkDocs}
  mk --> html[HTML pages]
  mk --> txt[llms.txt]
  mk --> copy[Markdown copies]

Diagrams follow the colour scheme: switch to dark mode and they are redrawn.

Hover a diagram (or tap it on a phone) for three buttons: open it full screen, copy its Mermaid source, and download it as an SVG file. Until a diagram is drawn, and in a browser without JavaScript, readers see its source text.

Sequence diagrams#

sequenceDiagram
  participant B as Browser
  participant S as Static host
  B->>S: GET /guide/
  S-->>B: index.html
  B->>S: GET /search/search_index.json (on ⌘K)
  S-->>B: JSON index
  Note over B: Search runs locally

Captions#

A title on the fence becomes the caption:

```mermaid title="Build pipeline"
flowchart LR
  a --> b
```
stateDiagram-v2
  direction LR
  [*] --> Config
  Config --> Files
  Files --> Nav
  Nav --> Pages
  Pages --> [*]
Build pipeline

Note

Mermaid is loaded as a JavaScript module, which browsers do not allow from file:// pages. Open the built site through a web server (mkdocs serve, python -m http.server) or any static host.

Note

If you use pymdownx.superfences with a custom fence for Mermaid, remove it: Sumi handles mermaid fences itself. To turn Sumi's handling off, set mermaid: false on the plugin.

Charts and mind maps#

Two more fences draw pictures from text. Like diagrams, their scripts ship with Sumi and load only when a chart or mind map is about to scroll into view.

Charts#

An echarts fence holds an Apache ECharts option, written in YAML or JSON. Sumi checks it when the site is built: a fence that is not valid YAML or JSON is left as a code block and the build prints a warning.

```echarts title="Build time for 1 000 pages (seconds)"
xAxis:
  type: category
  data: [MkDocs 1.6, MkDocs NG, NG + Sumi]
yAxis:
  type: value
series:
  - type: bar
    data: [18.1, 8.4, 12.0]
```
xAxis:
  type: category
  data: [MkDocs 1.6, MkDocs NG, NG + Sumi]
yAxis:
  type: value
series:
  - type: bar
    data: [18.1, 8.4, 12.0]
Build time for 1 000 pages (seconds)

Charts use the site's accent colour first, follow the colour scheme, and redraw when the page is resized. Set height on the fence to change the height in pixels (the default is 360):

```echarts height="240"
series:
  - type: pie
    radius: ["45%", "70%"]
    data:
      - {name: Markdown, value: 60}
      - {name: Templates, value: 15}
      - {name: Search index, value: 16}
      - {name: Other, value: 9}
```
tooltip: {}
series:
  - type: pie
    radius: ["45%", "70%"]
    data:
      - {name: Markdown, value: 60}
      - {name: Templates, value: 15}
      - {name: Search index, value: 16}
      - {name: Other, value: 9}

Without JavaScript, and in the Markdown copy of the page, readers see the YAML source.

Note

Options are data, not code: functions such as custom formatter callbacks cannot be written in YAML or JSON. Use template strings like formatter: "{b}: {c}" instead.

Mind maps#

A markmap fence draws a mind map from headings and nested lists. Click a node to fold or unfold its branch; drag to pan and scroll to zoom.

```markmap height="360"
# Sumi
## Write
- Callouts
- Tabs and steps
- Diagrams and charts
## Publish
- Landing page
- Blog
- Releases
## Read
- ⌘K search
- Dark mode
- Print edition
```

Sumi

  • Write
    • Callouts
    • Tabs and steps
    • Diagrams and charts
  • Publish
    • Landing page
    • Blog
    • Releases
  • Read
    • ⌘K search
    • Dark mode
    • Print edition

The outline is turned into a tree when the site is built. Without JavaScript, in print and in the Markdown copy, readers see it as a nested list.

Turning them off#

If another plugin already handles echarts or markmap fences, switch Sumi's handling off:

mkdocs.yml
plugins:
  - sumi:
      echarts: false
      markmap: false

Math#

Write TeX between dollar signs. Sumi typesets it with a bundled copy of KaTeX, loaded only on pages that contain a formula.

Inline formulas#

Euler's identity, $e^{i\pi} + 1 = 0$, links five constants.

Euler's identity, e^{i\pi} + 1 = 0, links five constants.

A dollar sign followed by a space, or preceded by a letter or digit, is not math — so prices such as $5 or $10 stay as written. Escape a literal dollar sign as \$.

Display formulas#

$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x)\, e^{-2\pi i x \xi}\, dx
$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x)\, e^{-2\pi i x \xi}\, dx

Multi-line environments work too:

\begin{aligned} \nabla \cdot \mathbf{E} &= \frac{\rho}{\varepsilon_0} \\ \nabla \times \mathbf{B} &= \mu_0 \mathbf{J} + \mu_0 \varepsilon_0 \frac{\partial \mathbf{E}}{\partial t} \end{aligned}

Compatibility#

If pymdownx.arithmatex is configured with generic: true, Sumi renders its output with the same KaTeX and turns its own $ parsing off. To disable math entirely, set math: false on the plugin.

Keys and icons#

Keyboard shortcuts#

Wrap a key combination in double plus signs:

Press ++Ctrl+Shift+P++ to open the command palette, or ++Esc++ to close it.

Press Ctrl+Shift+P to open the command palette, or Esc to close it.

For a single key in running text, plain <kbd> also works: Enter.

Icons#

Write :lucide-name: to insert any of the 2 000 Lucide icons, or :brand-name: for a brand logo. Icons are inlined as SVG at build time and take the colour of the surrounding text.

:lucide-rocket: Launch · :lucide-shield-check: Secure · :lucide-languages: Translated

Launch · Secure · Translated

Unknown names are left as text, so a typo is easy to spot.

Brand icons#

Icon Name Icon Name
brand-github brand-gitlab
brand-gitee brand-codeberg
brand-python brand-pypi
brand-docker brand-rust
brand-x brand-mastodon
brand-bluesky brand-discord
brand-wechat brand-zhihu
brand-bilibili brand-xiaohongshu
brand-youtube brand-rss

The full set also includes bitbucket, telegram, reddit, stackoverflow, npm, go, postgresql, markdown, readthedocs, matrix, qq, medium, devdotto, substack, facebook and instagram.

In templates and front matter#

The same names are used everywhere Sumi accepts an icon: landing page features, buttons, social links and cards.

extra:
  social:
    - icon: brand-github
      link: https://github.com/mkdocs-ng/mkdocs-sumi

Badges#

Put {.badge} right after bold or italic text to turn it into a badge. This is the standard attr_list syntax, so a renderer that does not know Sumi still shows the word in bold.

## Command palette **New**{.badge .new}

| Option | Status |
| --- | --- |
| `palette` | **Stable**{.badge .tip} |
| `brand` | **Beta**{.badge .warning} |
| `legacy_nav` | **Deprecated**{.badge .deprecated} |
Option Status
palette Stable
brand Beta
legacy_nav Deprecated

Tones#

A second class picks the colour. Without one, a badge is neutral grey.

Class Badge
(none) Default
.note, .info Note
.tip, .success Stable
.important, .new New
.warning Beta
.caution, .danger, .deprecated Removed
.accent Sumi

In headings#

A badge in a heading shrinks to fit, and stays out of the table of contents text.

Version 0.2 Latest#

## Version 0.2 **Latest**{.badge .tip}

Landing pages#

A landing page is a Markdown file with template: landing.html. Its sections are described in front matter, so the page stays data you can review in a diff — no HTML, no components to import. The home page of this site is one.

docs/index.md
---
template: landing.html
hero:
  eyebrow: "v2.0 is out"
  title: "Ship docs<br>*people read*."
  text: One sentence that explains the product.
  actions:
    - {text: Get started, link: guide/, icon: rocket, primary: true}
    - {text: GitHub, link: "https://github.com/me/project", icon: brand-github}
  install: pip install my-project
sections:
  - type: features
    title: Why it is different
    items:
      - {icon: zap, title: Fast, text: Builds in under a second., link: guide/speed/}
      - {icon: lock, title: Private, text: Runs entirely on your machine.}
  - type: cta
    title: Ready?
    actions:
      - {text: Read the guide, link: guide/, primary: true}
---

A link to a Markdown file, such as guide.md or guide/speed.md#cache, leads to the page built from it. Like a Markdown link, it is relative to the landing page, and the build warns when the file doesn't exist. Other links are relative to the site root, like nav entries. Text fields accept inline Markdown.

Hero#

eyebrowstring
A short label above the title, such as a release announcement.
titlestring
The headline. <br> breaks lines; *emphasis* is drawn in the accent colour. Defaults to the page title.
textstring
One or two sentences under the title. Defaults to site_description.
actionslist
Buttons: text, link, icon, primary.
installstring
A command shown in a click-to-copy box.
codeobject
A code card beside the text: title, lang, content.
imagestring
An image beside the text, if there is no code.

Sections#

Every entry in sections has a type and optional eyebrow, title, text, id and tone: soft (a tinted background).

featuresKeys items, columns
A grid of features, each with icon, title, text and an optional link.
splitKeys bullets, actions, code or image or markdown, reverse
Text on one side, an illustration on the other.
statsKeys items
Big numbers: value and label.
stepsKeys items
A numbered sequence: title and text.
faqKeys items
Questions and answers: q and a (Markdown).
ctaKeys actions
A closing call to action.
content
The Markdown body of the page. If sections is omitted, the body is shown under the hero.

Tip

Keep the page body for content that agents and search should see. The front-matter sections are visual; the Markdown copy of a landing page contains its title, text and body.

Blog#

Every Markdown file under docs/blog/ is a post. Sumi generates the blog index, keeps posts out of the sidebar, and writes an Atom feed. You don't need another plugin.

  • docs/
    • blog/
      • .authors.ymloptional: author names and avatars
      • index.mdoptional: the blog's title and intro
      • posts/
        • 2026-11-01-introducing-sumi.md
        • 2026-11-15-search.md

See it on this site's blog.

Writing a post#

docs/blog/posts/2026-11-01-introducing-sumi.md
---
date: 2026-11-01
authors: [xianpeng]
tags: [release, design]
description: Sumi 0.1 is out.
---

# Introducing Sumi

The first paragraph is the excerpt shown on the index.

<!-- more -->

The rest of the post.
datedate
Publication date. Defaults to the date of the last Git commit, or the file's modification time.
updateddate
Shown as "Updated" on the post.
authorslist
Author ids from .authors.yml, or plain names.
tagslist
Shown on the post and as filters on the index. categories is accepted too.
descriptionstring
The excerpt on the index and the summary in the feed. Without it, the text before <!-- more --> is used.
imagestring
A cover image, as a path from docs/. Shown at the top of the post, on its card in the card view, and as the preview when the post is shared. Set hide_cover: true to keep it off the post itself.
draftboolean
Build the page but leave it out of the index and the feed.

Authors#

docs/blog/.authors.yml
authors:
  xianpeng:
    name: Xianpeng Shen
    description: Maintainer
    avatar: https://github.com/shenxianpeng.png
    url: https://github.com/shenxianpeng

The format is the same as the Material blog plugin's, so an existing file can be reused.

The index page#

Without docs/blog/index.md, Sumi creates one titled "Blog". To add an introduction, create the file; any Markdown in it is shown above the list of posts. Tag filters appear when posts have tags, and a filter can be linked to as blog/#tag=release.

List and card views#

The index shows posts as a list, or as a grid of cards with cover images. Readers can switch with the buttons next to the tag filters; their choice is remembered. Pick the default with blog.layout:

mkdocs.yml
plugins:
  - sumi:
      blog:
        layout: cards    # or list (the default)

A post without an image gets a cover drawn from the accent colour and its first tag.

Feed#

An Atom feed is written to blog/feed.xml and linked from every page's <head>, so feed readers find it from any URL. It needs site_url to produce absolute links.

Options#

mkdocs.yml
plugins:
  - sumi:
      blog:
        dir: blog        # folder under docs/ that contains posts
        feed: true
        feed_items: 20
        layout: list     # or cards

Releases#

A page with template: releases.html lists versions with their notes and downloadable assets. Each asset shows its size and a SHA-256 checksum that copies on click. See this site's releases.

docs/releases.md
---
template: releases.html
releases: releases.yml
---

# Releases

Download the latest version, or browse the history.

releases is either a list in the front matter or a path to a YAML file relative to docs/.

docs/releases.yml
- version: 0.2.0
  date: 2026-12-01
  summary: Versioned docs and a Japanese interface.
  notes: |
    - **New:** `version_provider: mike`.
    - **Fixed:** tabs no longer jump when switching.
  assets:
    - name: mkdocs_sumi-0.2.0-py3-none-any.whl
      url: https://example.com/mkdocs_sumi-0.2.0-py3-none-any.whl
      size: 1.9 MB
      sha256: 9f2c0a…
- version: 0.1.0
  date: 2026-11-01
  notes: First release.

Fields#

versionstring
Version number; also the anchor, so releases/#v0-2-0 links to it.
titlestring
Shown instead of the version, if set.
datedate
Release date.
statusstring
latest, stable, prerelease or archived. The first release defaults to latest.
summarystring
One line under the heading.
notesstring
Release notes in Markdown.
assetslist
Downloads: name, url, size, sha256.

The page's own Markdown is shown above the list, so you can add installation instructions or a support policy.

Print edition#

Sumi writes the whole site as one long page at /print/, in navigation order, with a cover and a table of contents. Open it and print to PDF for an offline manual. Open this site's print edition.

Every page also prints cleanly on its own: the header, sidebars and buttons are hidden, collapsed callouts are expanded, tabs are shown one after another, and links show their address.

How it works#

The print edition is built from the same HTML as the pages, after they are rendered. Links between pages become links within the document; images and assets are rebased so they resolve from /print/.

Options#

mkdocs.yml
plugins:
  - sumi:
      print:
        enabled: true
        path: print/

Use the Print command in the page actions menu, or press ⌘ K and type > print.

Tip

To leave a page out of the print edition, add print: false to its front matter.

Theme options#

Theme options are set under theme: in mkdocs.yml. All of them are optional.

mkdocs.yml
theme:
  name: sumi
  palette: auto
  accent: indigo
  logo: assets/logo.svg
  announcement: 'Sumi 0.1 is out — <a href="blog/">read the post</a>'

Options#

palettestringDefault auto
light, dark or auto (follow the operating system). Readers can switch at any time; their choice is remembered.
accentstringDefault vermilion
The colour of links, highlights and the active tab: vermilion, indigo, moss, plum, ochre, teal, ink, or any CSS colour.
localestringDefault en
The interface language: en, zh, zh_TW. See Languages.
logostring
An image in docs/ for the header. The Sumi mark is used if unset.
logo_darkstring
A second header image for the dark palette. logo is used in both if unset.
faviconstring
An image in docs/ for the browser tab.
social_imagestring
The preview image shown when a page is shared on X, Slack, Mastodon and the like: an image in docs/ (about 1200×630, needs site_url) or a full URL. A page can use its own with image: in its front matter.
brandstringDefault site_name
HTML for the header brand, e.g. "MkDocs<b>ng</b>".
announcementstring
HTML for a dismissible bar above the header. A new message reappears for readers who dismissed an older one.
navigation_tabsbooleanDefault true
Show top-level sections as tabs in the header. When false, the whole navigation is in the sidebar.
toc_depthintegerDefault 3
The deepest heading level in the page table of contents.
hidelistDefault []
Parts to hide on every page: toc, sidebar, breadcrumbs, actions, pager, footer.
version_providerstring
mike to show a version menu. See Navigation.
repo_iconstring
Override the repository icon, e.g. brand-gitlab. Detected from repo_url by default.
keyboardbooleanDefault true
Enable single-key shortcuts. ⌘ K always works.
creditbooleanDefault true
Show "Built with MkDocs and Sumi" in the footer.

Accent colours#

Name Light Dark
vermilion #c4432b #ef7258
indigo #2f5d93 #86b4ea
moss #4b6b2f #9ccc6e
plum #7a3b74 #dc93d4
ochre #9a6512 #e7b45a
teal #1f6f6a #6fd0c6
ink #2a2926 #e8e4dc

A named accent has a tuned variant for dark mode. A custom CSS colour is used in both modes.

Per-page options#

Front matter on a page can change its layout:

hide
Like the theme option, for one page: hide: [toc, pager]. navigation is accepted as an alias for sidebar.
layout
wide removes both sidebars for a full-width page.
template
landing.html, blog.html or releases.html. See Pages.
description
Used for the <meta name="description">, link previews, cards and the blog index.
mkdocs.yml
copyright: © 2026 The MkDocs NG project
extra:
  social:
    - icon: brand-github
      link: https://github.com/mkdocs-ng
      name: GitHub
    - icon: fontawesome/brands/x-twitter   # Material names work too
      link: https://x.com/mkdocs

Custom CSS#

Every colour, font and size is a CSS variable. Add a stylesheet with extra_css:

docs/assets/extra.css
:root {
  --sumi-font: "IBM Plex Sans", system-ui, sans-serif;
  --sumi-content-w: 820px;
}
[data-theme="dark"] {
  --sumi-bg: #0f1115;
}
mkdocs.yml
extra_css:
  - assets/extra.css

Plugin options#

The sumi plugin adds the Markdown components, the blog, the print edition, the Markdown copy button and Git dates. The theme needs it; you can use it without options.

mkdocs.yml
plugins:
  - search
  - llms_txt
  - sumi:
      git_dates: true
      assistant_links: [claude, chatgpt]

Options#

languagestringDefault theme.locale
Language of default callout titles and generated text.
mathbooleanDefault true
Parse $…$ and $$…$$ and load KaTeX on pages that use them.
mermaidbooleanDefault true
Render mermaid fences and load Mermaid on pages that use them.
echartsbooleanDefault true
Render echarts fences (YAML or JSON options) as charts. See Charts.
markmapbooleanDefault true
Render markmap fences (Markdown outlines) as mind maps. See Mind maps.
markdown_copybooleanDefault true
Show Copy as Markdown and View as Markdown when a Markdown copy of the page exists.
assistant_linkslistDefault []
Add "Open in Claude" or "Open in ChatGPT" to the page menu: claude, chatgpt. Off by default, because they send the page address to a third party.
git_datesbooleanDefault false
Show "Last updated" from the last Git commit that touched each page. One git log runs per build.
blog.enabledbooleanDefault true
Treat files under blog.dir as posts.
blog.dirstringDefault blog
The blog folder under docs/.
blog.feedbooleanDefault true
Write an Atom feed to blog/feed.xml.
blog.feed_itemsintegerDefault 20
Number of posts in the feed.
blog.authorsmappingDefault {}
Authors, if you prefer not to use .authors.yml.
print.enabledbooleanDefault true
Write the print edition.
print.pathstringDefault print/
Where to write it.

Markdown extensions#

The plugin enables a small set of Python-Markdown extensions that the components rely on. They are listed in Writing. Your own markdown_extensions entries take precedence: if you configure one of them with options, your options are used.

Git dates in CI#

Shallow clones only contain the last commit, which makes every page look updated today. Fetch the full history before building:

.github/workflows/docs.yml
- uses: actions/checkout@v5
  with:
    fetch-depth: 0

Navigation#

Sumi builds its navigation from MkDocs' nav, or from your folders when there is no nav.

mkdocs.yml
nav:
  - Home: index.md
  - Guide:
      - guide/index.md          # section landing page
      - Install: guide/install.md
      - Configure: guide/configure.md
  - Reference:
      - reference/index.md
      - CLI: reference/cli.md
  - Blog: blog/index.md

Tabs and sidebar#

  • Each top-level section is a tab in the header. The sidebar shows the pages of the active tab only.
  • Top-level pages, such as Home and Blog above, are tabs as well.
  • A section whose first entry is an index.md without a title uses that page as its landing: the tab, the sidebar heading and the section name all link to it.
  • Nested sections are collapsible. The section containing the current page is open.

On small screens, the tabs and the sidebar move into a drawer opened from the menu button.

With navigation_tabs: false, the whole navigation is shown in the sidebar.

Table of contents#

Headings up to toc_depth (level 3 by default) are listed on the right, with the current section highlighted as you scroll. On narrow screens the list is folded at the top of the page.

Previous and next#

Links to the previous and next page follow the navigation order. [ and ] move between pages; j and k move between headings.

Versions#

Sumi reads the versions.json file that mike writes, shows a version menu in the header and a notice on old versions.

mkdocs.yml
theme:
  name: sumi
  version_provider: mike
mike deploy --push --update-aliases 0.2 latest
mike set-default --push latest

Languages#

On a site with several languages, the header shows a language menu, and the command palette gets a Switch to … command for each language. With MkDocs NG 1.11 or later, list the languages under languages and the menu links each page to the same page in the other languages; see Languages.

With an older MkDocs, or to link to other sites, list them under extra.alternate yourself:

mkdocs.yml
extra:
  alternate:
    - name: English
      link: /en/
      lang: en
    - name: 简体中文
      link: /zh/
      lang: zh

With repo_url and edit_uri, each page shows Edit this page at the bottom and in the page menu.

mkdocs.yml
repo_url: https://github.com/me/project
edit_uri: edit/main/docs/

Languages#

Sumi's interface — buttons, labels, the search palette, dates and default callout titles — is available in English, Simplified Chinese and Traditional Chinese.

mkdocs.yml
theme:
  name: sumi
  locale: zh        # en, zh (zh_CN), zh_TW

CJK typography#

When the page language is Chinese, Japanese or Korean, Sumi uses a system font stack suited to the script, a slightly larger line height and no letter-spacing on headings. Search matches CJK text by substring (see Search).

Callout titles#

Default callout titles follow the language: > [!TIP] is titled Tip in English and 提示 in Chinese. A custom title is always used as written.

Multilingual sites#

With MkDocs NG 1.11 or later, one mkdocs.yml holds every language. The first language is built at the root of the site and the others into /zh/ and so on; each one can change any setting, such as site_name or nav. Sumi takes its interface language from each language's code.

mkdocs.yml
site_url: https://example.com/

languages:
  en: English
  zh:
    name: 简体中文
    site_name: 我的项目
    nav:
      - 首页: index.md
      - 指南: guide.md

Put each translation next to the original page, as guide.zh.md next to guide.md, or give the language a folder of its own with docs_dir: docs-zh. mkdocs build builds every language and mkdocs serve previews them all; add --language zh to work on one.

What Sumi does on such a site:

  • The language menu in the header, and the Switch to … commands in the command palette, link to the same page in the other language.
  • A page that isn't translated yet is shown in the default language, with a note at the top that says so. Its text is marked with its own lang, so screen readers and browsers read it in the right language.
  • Each page lists its translations with <link rel="alternate" hreflang="…">, so search engines show readers the version in their language.
  • Search, llms.txt and the print edition are built for each language.

See Multi-language Sites in the MkDocs NG docs for all the settings. This site and sumi-starter are built this way.

With an older MkDocs, build one site per language and link them with extra.alternate; see Navigation.

Adding a language#

Interface strings live in one Python file, mkdocs_sumi/i18n.py. A missing string falls back to English. Pull requests for new languages are welcome.

Template overrides#

Sumi is a standard MkDocs theme, so any template can be overridden with custom_dir:

mkdocs.yml
theme:
  name: sumi
  custom_dir: overrides
  • overrides/
    • main.htmlextend blocks for every page
    • partials/
      • footer.htmlreplace one partial

Extending blocks#

Create overrides/main.html and extend base.html:

overrides/main.html
{% extends "base.html" %}

{% block extrahead %}
  <meta name="robots" content="noindex">
{% endblock %}
Block Contains
site_meta <meta> and <link> tags in <head>
htmltitle The <title> element
styles Stylesheets
analytics The analytics snippet
extrahead Empty; for your own tags
header The site header
layout Sidebar, article and table of contents
content The article
page_content The rendered Markdown
article_footer Last updated, edit link, previous / next
footer The site footer
scripts Scripts at the end of <body>

Partials#

Partial Purpose
partials/header.html Logo, tabs, search button, menus
partials/logo.html The logo only
partials/breadcrumbs.html The path above the title
partials/page-actions.html Copy as Markdown and the page menu
partials/toc.html Table of contents
partials/article-footer.html Below the article
partials/footer.html Site footer
partials/palette.html Search and command palette
partials/analytics.html Analytics

Analytics#

Google Analytics is supported with the same keys as Material:

mkdocs.yml
extra:
  analytics:
    provider: google
    property: G-XXXXXXXXXX

For another provider, add its snippet to the analytics block in overrides/main.html.

Note

Analytics is the one feature that contacts a third party. It is off unless configured.

For plugin authors#

Material for MkDocs is in maintenance mode, with critical fixes through 2027-05-05. Many plugins were written and tested only against it. This page is for their authors: what already works in Sumi, what needs a small change, and how to get your plugin listed.

Sumi is a standard MkDocs theme. A plugin that only uses MkDocs events and writes plain HTML into the page works without changes.

Does my plugin need changes?#

Your plugin… In Sumi
Uses MkDocs events and writes HTML into page.content Works.
Ships CSS that reads --md-* variables Works: Sumi maps the common ones to its own colours, in both palettes.
Switches its dark CSS on [data-md-color-scheme="slate"] Works: Sumi sets that attribute next to its own data-theme.
Writes !!! note admonitions, <details>, tables or highlighted code Works: Sumi styles them like its own callouts and code blocks.
Scopes CSS to .md-typeset Change: add .sumi-prose, the class of Sumi's content container.
Runs JavaScript on document$ (Material's instant loading) Change: Sumi loads every page normally; run on DOMContentLoaded.
Checks config.theme.name == "material" Change: accept "sumi" as well, or test for the feature you need.
Imports from the material package or overrides Material partials Change: see Templates.

Detect the theme#

plugin.py
def on_config(self, config):
    self.is_sumi = config.theme.name == "sumi"

Prefer this over checking for installed packages: a site can have Material installed and still build with Sumi.

Styles#

Rendered Markdown lives in <div class="sumi-content sumi-prose">. Scope your CSS to it the same way you scoped it to .md-typeset:

my-plugin.css
:is(.md-typeset, .sumi-prose) .my-widget { margin: 1em 0; }

Use Sumi's tokens when you want an exact match, and keep Material's as the fallback so one stylesheet serves both themes:

.my-widget {
  border: 1px solid var(--sumi-line, var(--md-default-fg-color--lightest));
  background: var(--sumi-bg-elev, var(--md-default-bg-color));
  color: var(--sumi-fg-2, var(--md-default-fg-color--light));
}
Token Use
--sumi-fg, --sumi-fg-2, --sumi-fg-3 Text: body, secondary, muted
--sumi-bg, --sumi-bg-elev, --sumi-bg-soft, --sumi-bg-sunken Page, raised panels, subtle fills, wells
--sumi-line, --sumi-line-2 Borders: hairline and stronger
--sumi-accent The accent colour chosen with theme.accent
--sumi-link Links
--c-note, --c-tip, --c-important, --c-warning, --c-caution Status colours, the same as callouts
--sumi-code-bg, --sumi-code-inline Code block and inline code backgrounds
--sumi-font, --sumi-font-mono Text and code fonts
--sumi-radius, --sumi-radius-sm Corner radii

All tokens change with the palette, so CSS that uses them needs no dark-mode rules. If you write your own, key them on [data-theme="dark"].

To add your stylesheet, append it to config.extra_css in on_config and copy the file in on_post_build, as you would for Material. Sumi loads extra_css after its own CSS.

Scripts#

Sumi has no instant loading: each link is a normal page load, so a script that runs on DOMContentLoaded runs on every page. When the reader switches between light and dark, Sumi updates data-theme on <html> and fires an event:

document.addEventListener("sumi:theme", (event) => {
  redraw(event.detail.theme);  // "light" or "dark"
});

data-theme is set before the page is drawn, so a script can read it at any time. Don't rely on Sumi's own script having run first: it is deferred.

Templates#

Sumi has the usual MkDocs blocks. A plugin or user override that extends Material's main.html maps like this:

Material block In Sumi
extrahead, htmltitle, site_meta, styles, analytics Same name
scripts, content, header, footer Same name
announce The theme.announcement option
outdated Built in with theme.version_provider: mike
tabs, hero Header tabs are built in; use a landing page for a hero

Sumi's partials have different names and contents from Material's; see Template overrides. If your plugin replaces a Material partial, such as the "last updated" line, write the information into the page instead, or ask for a hook in an issue.

Features Sumi already has#

Some plugins exist to fill a gap that Sumi covers itself. If yours is one of them, document how it fits next to Sumi's version rather than duplicating it.

Material plugin In Sumi
blog The blog reads the same folder, front matter and .authors.yml
tags Tags on blog posts, with filters on the index
search MkDocs' search plugin, shown in Sumi's command palette
privacy, offline Not needed: fonts, icons and scripts are bundled, nothing is fetched at read time
social Not built in. Sumi writes og:title, og:description and twitter:card, and writes og:image only when social_image or a page image: is set, so a social card plugin can add its own image without duplicates

Test against Sumi#

  1. Build a small site with theme: sumi and plugins: [search, sumi, your-plugin], using mkdocs build --strict.
  2. Check the output in both palettes and on a narrow screen.
  3. Open /print/, the print edition, if your plugin adds content to pages.

Get listed#

Open an issue with your plugin's name and a screenshot of it in Sumi. Plugins that work are added to the migration guide. If something in Sumi stands in your way, say so in the same issue: a missing block or token is easier for us to add than for you to work around.

Plugins Sumi styles#

These plugins write HTML that Sumi styles to match the rest of the site.

mkdocstrings#

API pages from mkdocstrings get Sumi's code font and colours: signatures, symbol badges (mod, class, func, meth, attr, type, param), labels, nested members, section tables and lists, parameter headings, backlinks and a quiet "Source code" panel. These options suit Sumi well:

mkdocs.yml
plugins:
  - search
  - sumi
  - mkdocstrings:
      handlers:
        python:
          options:
            show_root_heading: true
            show_symbol_type_heading: true
            show_symbol_type_toc: true
            separate_signature: true
            show_signature_annotations: true
            signature_crossrefs: true

Docs for people and agents#

More and more readers of documentation are not people. Coding agents, assistants and search tools read your pages to answer questions about your project, and they read HTML badly: navigation, scripts and styling are noise to them. Sumi publishes every page twice — as a page for people and as plain Markdown for programs.

What Sumi publishes#

Every output is a static file. None requires a server.
Output Where For
HTML page guide/install/ People
Markdown copy guide/install.md Agents reading one page
llms.txt site root Agents discovering the site
llms-full.txt site root Agents loading everything at once (opt-in)
Atom feed blog/feed.xml Feed readers
Print edition print/ Paper, PDF, offline reading

The Markdown copy and llms.txt come from the llms_txt plugin that ships with MkDocs NG. Enable it next to sumi:

mkdocs.yml
site_url: https://example.com/docs/   # needed for absolute links in llms.txt

plugins:
  - search
  - llms_txt:
      full_output: true   # also write llms-full.txt
  - sumi

Try it on this site: open llms.txt, or choose View as Markdown in the menu at the top of this page.

Discovery#

Each HTML page announces its Markdown copy in <head>:

<link rel="alternate" type="text/markdown" href="ai.md" title="Markdown">
<link rel="alternate" type="text/plain" href="../llms.txt" title="llms.txt">

An agent that lands on any page can find the clean version without guessing URLs.

Copy as Markdown#

The Copy as Markdown button at the top of each page copies the page source — the same text an agent reads — so you can paste it into a chat. The menu next to it can also open the Markdown copy, print the page and, if you enable it, open the page in an assistant:

mkdocs.yml
plugins:
  - sumi:
      assistant_links: [claude, chatgpt]

Note

Assistant links are off by default: they send the page address to a third-party service when clicked.

Why native Markdown matters here#

Components written in a private syntax — MDX, shortcodes, HTML widgets — reach an agent either as raw markup it has to interpret or not at all. Sumi's components are ordinary Markdown with an attribute line, so the Markdown copy reads naturally: a callout is a quote, steps are a numbered list, tabs are consecutive code blocks with labels.

Landing pages are the one place where content lives in front matter. For those pages, Sumi writes a Markdown copy that lists the hero, features, steps and questions as headings and lists.

Design principles#

Sumi (墨) is the ink of East Asian painting and calligraphy. One ink, ground to different strengths, produces every shade on the page. The theme follows the same idea: warm paper, black ink, a single vermilion accent like a seal, and nothing that does not carry information.

Content stays Markdown#

Documentation outlives the tools that render it. A page written for Sumi must still make sense on GitHub, in an editor and in a terminal, so every component is written in Markdown's own vocabulary — blockquotes, lists, tables, images and fences — with at most one {…} line of attributes. If you leave Sumi tomorrow, your content leaves with you.

This rules out shortcodes, MDX and inline HTML widgets, and it is why the component set is small. Each component exists because technical documentation needs it often: warnings, procedures, alternatives, references, layouts and diagrams.

Everything is local#

A built Sumi site makes no network requests that you did not configure. Fonts (Inter and JetBrains Mono), 2 000 icons, the search index, Mermaid and KaTeX are part of the theme and copied into your site. That means:

  • the site works behind a firewall, on an intranet and from a USB stick;
  • readers are not tracked by a font or script CDN;
  • a build today and a build in five years produce the same page.

Pay only for what you use#

The core of the theme is one stylesheet and one script with no framework. Heavier runtimes are loaded on demand: Mermaid only when a diagram nears the viewport, and only the code for that diagram type, KaTeX only on pages with a formula, the search index only when the palette is opened. Vendor files are copied into the site only if some page uses them.

Degrade gracefully#

Every page is readable without JavaScript: navigation is links, collapsible sections are <details>, tabs show one after another, file trees are nested lists. JavaScript adds convenience — search, tab sync, copy buttons, scroll spy — never content.

People and agents are both readers#

Every page is published as HTML for people and as Markdown for programs, and each HTML page links to its Markdown twin. See Docs for people and agents.

One design language#

Docs, blog, landing page, release downloads and print share one set of tokens: type scale, spacing, colours and components. A project site built with Sumi looks like one product, not a docs theme with a marketing page bolted on.

Credits#

Sumi's principles were inspired by Oink, a documentation distribution for Hugo by Feng Ruohang, and by the long tradition of MkDocs themes — especially Material for MkDocs, whose configuration Sumi reads so that existing sites can switch. Sumi's code and design are its own.

Releases#

Install the latest release from PyPI:

pip install --upgrade mkdocs-sumi

Sumi follows semantic versioning. Until 1.0, minor versions may change template blocks; configuration keys and Markdown syntax stay compatible.

Blog#

Releases, design notes and guides from the Sumi project.

Esc
↵ to open ↑↓ to move Esc to close
Keyboard shortcuts
Ctrl K /
Open search and commands
t
Toggle dark mode
[ ]
Previous / next page
j k
Previous / next heading
?
Show this help