Skip to content

Fix: MkDocs Not Working — Material Theme, Navigation, and Plugin Errors

FixDevs ·

Quick Answer

How to fix MkDocs errors — Config value 'theme' must be a string or dict, mkdocstrings not generating API docs, navigation pages missing, mkdocs serve port already in use, GitHub Pages deploy failed, and broken links not detected.

The Error

You configure MkDocs Material and the build fails with a confusing message:

ERROR - Config value 'theme': Unrecognised theme name: 'material'.
The theme has to be installed.

Or mkdocstrings doesn’t generate API docs from your Python code:

::: mypackage.MyClass
$ mkdocs build
# Output: literally renders "::: mypackage.MyClass" as text

Or your nav entries don’t appear in the sidebar:

nav:
  - Home: index.md
  - Guide: guide.md
  - API: api.md

But the sidebar shows nothing — or shows different pages than expected.

Or mkdocs serve says the port is in use:

[I 250424 10:00:00 server:335] OSError: [Errno 48] Address already in use

Or GitHub Pages deploy works locally but fails in CI:

Permission denied (publickey).
fatal: Could not read from remote repository.

MkDocs is the Markdown-first alternative to Sphinx — simpler config, faster builds, great for project docs and tutorials. The Material theme (mkdocs-material) is the de facto standard, providing a polished UI with search, dark mode, and navigation. But the plugin ecosystem and configuration grammar create specific failure modes. This guide covers each.

Why This Happens

MkDocs has a small core and relies on plugins and themes installed as separate packages. The theme: material line in mkdocs.yml references a theme by name — but the package providing it must be installed separately. Same for plugins (mkdocstrings, mike, awesome-pages). MkDocs doesn’t install dependencies for you.

The navigation is built from either explicit nav: entries in mkdocs.yml or auto-discovered from the file structure. Mixing the two patterns confuses MkDocs — it uses your explicit nav but ignores files not listed.

Fix 1: Installing MkDocs and Material

# Core MkDocs
pip install mkdocs

# Material theme (recommended)
pip install mkdocs-material

# Common plugins
pip install mkdocstrings[python]    # API doc generation from Python source
pip install mkdocs-awesome-pages-plugin   # Automatic nav from file structure
pip install mike                      # Version management

Minimal mkdocs.yml:

site_name: My Project
site_url: https://myproject.example.com/
repo_url: https://github.com/me/myproject
repo_name: me/myproject

theme:
  name: material
  features:
    - navigation.tabs
    - navigation.sections
    - navigation.expand
    - content.code.copy
    - content.code.annotate
    - search.highlight
  palette:
    - scheme: default
      primary: indigo
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode
    - scheme: slate
      primary: indigo
      toggle:
        icon: material/brightness-4
        name: Switch to light mode
  icon:
    repo: fontawesome/brands/github

markdown_extensions:
  - admonition
  - pymdownx.details
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.highlight:
      anchor_linenums: true
  - pymdownx.inlinehilite
  - pymdownx.snippets
  - attr_list
  - md_in_html
  - tables
  - toc:
      permalink: true

plugins:
  - search
  - mkdocstrings:
      handlers:
        python:
          options:
            show_source: true
            show_root_heading: true
            heading_level: 2

Build and serve:

mkdocs serve         # Start dev server at http://127.0.0.1:8000
mkdocs build         # Build static HTML to ./site/
mkdocs build --strict   # Fail on warnings (use for CI)

Common Mistake: Copying theme: name: material config from a tutorial without pip install mkdocs-material. The error message is helpful (“theme has to be installed”), but it’s still the #1 first-time MkDocs issue. Always check that every theme and plugin in mkdocs.yml has a matching pip install.

Fix 2: Project Layout and File Discovery

my-project/
├── mkdocs.yml
├── docs/
│   ├── index.md
│   ├── guide/
│   │   ├── installation.md
│   │   ├── quickstart.md
│   │   └── advanced.md
│   └── api/
│       └── reference.md
└── site/   (build output, gitignored)

MkDocs auto-discovers everything under docs/ and builds a nav from the file structure. To customize, declare an explicit nav:

nav:
  - Home: index.md
  - Guide:
      - Installation: guide/installation.md
      - Quickstart: guide/quickstart.md
      - Advanced: guide/advanced.md
  - API Reference: api/reference.md

Default nav rules (when nav: is omitted):

  • Files are sorted alphabetically
  • index.md becomes the section landing page
  • Underscores and hyphens are converted to spaces, title-cased
  • File names like 01-intro.md lose the prefix

mkdocs-awesome-pages-plugin — control nav from per-folder config:

pip install mkdocs-awesome-pages-plugin
# mkdocs.yml
plugins:
  - awesome-pages
# docs/guide/.pages
title: User Guide
nav:
  - installation.md
  - quickstart.md
  - advanced.md
  - troubleshooting.md

Far easier than maintaining mkdocs.yml nav when you have many files.

Fix 3: mkdocstrings — Auto API Docs

pip install mkdocstrings[python]
# mkdocs.yml
plugins:
  - mkdocstrings:
      handlers:
        python:
          paths: [src]   # Where to find your Python code
          options:
            show_source: true
            show_root_heading: true
            members_order: source
            docstring_style: google
            show_signature_annotations: true
            separate_signature: true
            line_length: 80

Use in Markdown:

# API Reference

## MyClass

::: mypackage.MyClass
    options:
      heading_level: 3
      show_root_full_path: false

## Utility Functions

::: mypackage.utils
    options:
      members:
        - fetch
        - parse

Common docstring styles (set via docstring_style):

StyleFormat
googleArgs:, Returns:, Raises:
numpyParameters\n----------, Returns\n-------
sphinx:param x:, :returns:, :raises:

Pro Tip: Pick a docstring style and enforce it project-wide. Mixed styles render inconsistently — some sections show fields nicely, others render as plain text. The docstring_style: google is the most readable in source code; the numpy style is preferred in scientific Python.

Common Mistake: Omitting paths: and finding mkdocstrings can’t import your package. Without paths:, it looks in the default Python path — which often misses your project source. Set paths: [src] (or wherever your package lives) explicitly.

Fix 4: Navigation Features (Material Theme)

theme:
  name: material
  features:
    - navigation.tabs              # Top-level tabs
    - navigation.tabs.sticky        # Tabs always visible
    - navigation.sections           # Expand top-level
    - navigation.expand             # Expand all by default
    - navigation.indexes            # Section index pages
    - navigation.top                # "back to top" button
    - navigation.footer             # Prev/next page footer links
    - navigation.tracking           # Update URL with anchor as you scroll
    - toc.follow                    # TOC scrolls with content
    - search.suggest                # Search suggestions
    - search.highlight              # Highlight search matches
    - content.code.copy             # Copy button on code blocks
    - content.code.annotate         # Inline code annotations
    - content.tabs.link             # Linked content tabs

Section index pages with navigation.indexes:

docs/
├── guide/
│   ├── index.md         ← This becomes the "Guide" section landing page
│   ├── installation.md
│   └── quickstart.md

When navigation.indexes is enabled, clicking “Guide” in the nav shows guide/index.md instead of expanding the section without content.

Hide a page from nav while keeping it accessible:

nav:
  - Home: index.md
  - Guide: guide.md
# changelog.md exists but not in nav — still accessible at /changelog/

Or via front matter:

---
hide:
  - navigation
  - toc
---

# Standalone Page

Fix 5: mkdocs serve Port Conflicts

OSError: [Errno 48] Address already in use

Default port 8000 is taken. Use a different port:

mkdocs serve --dev-addr 127.0.0.1:8001
mkdocs serve -a 0.0.0.0:8080   # Bind to all interfaces (for VM/Docker)

Find what’s using port 8000:

lsof -i :8000        # macOS/Linux
netstat -ano | findstr :8000   # Windows

# Kill the process
kill $(lsof -ti :8000)

For port conflict patterns, see port 3000 already in use.

mkdocs serve doesn’t auto-reload on config changes by default — only Markdown changes. Force a full reload:

mkdocs serve --watch mkdocs.yml --watch src/
# Now changes to mkdocs.yml or your source code trigger rebuilds

Fix 6: Admonitions and Code Block Features

Material supports a wide range of callouts via pymdownx.details:

!!! note "Optional Title"
    This is a note callout.

!!! warning
    This is a warning.

!!! danger "Important"
    This is critical.

!!! tip
    Pro tip content.

??? abstract "Collapsible"
    This admonition is collapsible (starts collapsed).

???+ info "Expanded by default"
    Starts expanded.

Admonition types: note, abstract, info, tip, success, question, warning, failure, danger, bug, example, quote.

Code blocks with syntax highlighting and titles:

```python title="example.py" linenums="1" hl_lines="2-3"
def hello():
    name = "World"
    print(f"Hello, {name}!")

**Code block with line annotations:**

```markdown
```python
def fetch(url):
    response = requests.get(url, timeout=30)  # (1)!
    return response.json()  # (2)!
  1. Always set a timeout for HTTP requests.
  2. Assumes the response is JSON.

The `(1)!` syntax creates clickable annotations — hovering shows the explanation.

**Content tabs:**

```markdown
=== "Python"
    ```python
    print("Hello")
    ```

=== "JavaScript"
    ```javascript
    console.log("Hello");
    ```

=== "Go"
    ```go
    fmt.Println("Hello")
    ```

Fix 7: GitHub Pages Deployment

mkdocs gh-deploy
# Builds, commits to gh-pages branch, pushes

Common deploy errors:

fatal: Could not read from remote repository.
Permission denied (publickey).

The local git user can’t push. Either:

  • Use HTTPS: git remote set-url origin https://github.com/me/repo.git
  • Configure SSH keys (most reliable for CI)
  • Use a personal access token

Deploy via GitHub Actions (recommended):

# .github/workflows/docs.yml
name: Deploy MkDocs

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # Required for git-revision-date plugin

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install mkdocs-material mkdocstrings[python] mkdocs-awesome-pages-plugin

      - name: Configure git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Deploy
        run: mkdocs gh-deploy --force

Set GitHub Pages source to “Deploy from a branch” → gh-pages branch in repo settings.

Common Mistake: Setting up GitHub Pages but forgetting to enable it in repo settings. The Actions workflow succeeds and pushes to gh-pages, but the page returns 404. Always verify Settings → Pages → Source is set to gh-pages branch (or main /docs if you prefer).

Fix 8: Custom Domain and Versioning

Custom domain:

# mkdocs.yml
site_url: https://docs.myproject.com/
# docs/CNAME (one line, no quotes)
docs.myproject.com

The CNAME file is automatically copied to the build output and GitHub Pages reads it.

Multi-version docs with mike:

pip install mike
# Deploy v1.0 docs
mike deploy --push --update-aliases 1.0 latest

# Deploy v2.0 — make it the new default
mike deploy --push --update-aliases 2.0 latest
mike set-default --push latest
# mkdocs.yml — add version selector
extra:
  version:
    provider: mike
    default: latest

mike keeps multiple versions of your docs on gh-pages — users can switch between them via a version dropdown.

Pro Tip: Use mike from day one if you’re documenting a library that follows semver. Switching from single-version to multi-version mid-project means rewriting URLs and breaking external links. Setting up mike for v1 is trivial; migrating later is painful.

Still Not Working?

MkDocs vs Sphinx

  • MkDocs — Markdown-first, smaller, faster builds. Best for prose-heavy project docs and tutorials.
  • Sphinx — More features (intersphinx, complex API doc generation), RST-first. Best for cross-referenced Python library docs. See Sphinx not working.

For Python library docs with extensive API references, Sphinx is more mature. For everything else (project docs, tutorials, marketing-style sites), MkDocs Material is faster to set up and modify.

Search Configuration

Default search works but is basic. Configure for better results:

plugins:
  - search:
      separator: '[\s​\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
      lang:
        - en
        - ja

For multilingual docs, list all languages in lang: — search separately indexes content for each.

Includes and Snippets

Reuse content across pages:

markdown_extensions:
  - pymdownx.snippets:
      base_path: docs/snippets
# Main Page

--8<-- "common-warning.md"

This includes docs/snippets/common-warning.md inline at build time.

mkdocs build --strict
# Fails on any warning (including broken internal links)

Use in CI to catch broken links before deploy. For broader link checking (external URLs), use lychee or linkcheck as a separate step.

Combining with Other Frameworks

If your docs include Python code that’s also tested, use mkdocstrings together with pytest --doctest-modules to keep examples accurate. For pytest patterns that integrate with docs, see pytest fixture not found.

For Sphinx as the alternative when you need stronger API doc features, see Sphinx not working. For pre-commit hooks that validate MkDocs config syntax before commits, see pre-commit not working.

Diagrams with Mermaid

Material supports Mermaid diagrams natively:

markdown_extensions:
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
```mermaid
graph LR
    A[Request] --> B{Auth?}
    B -->|Yes| C[Handler]
    B -->|No| D[401]
    C --> E[Response]

The diagram renders in the browser via Mermaid.js — no separate build step. Great for architecture overviews and flow diagrams.

### Social Cards

Material can auto-generate Open Graph images for each page:

```yaml
plugins:
  - social:
      cards: true
      cards_layout_options:
        background_color: "#0066CC"
        color: "#FFFFFF"

Requires pip install "mkdocs-material[imaging]" and ImageMagick — but produces shareable OG cards for every page automatically.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles