From 643a1060effcbd7241c38d4510414378096a269c Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 28 Apr 2026 03:18:36 +0300 Subject: [PATCH 1/3] Use index page titles for auto-generated section names When the 'nav' configuration is not explicitly set, MkDocs generates navigation sections from directory names. This change updates those auto-generated section titles to use the index page's title instead of the raw directory name when an index page exists in the section. For example, an 'about/' directory with 'about/index.md' whose title is 'About this Project' will now show 'About this Project' as the section name instead of 'about'. This only applies after pages have been read/rendered (titles are resolved from metadata or headings), and only affects sections that contain an index page. Fixes #3656, #3356 --- mkdocs/commands/build.py | 9 ++++++- mkdocs/structure/nav.py | 23 ++++++++++++++++++ mkdocs/tests/structure/nav_tests.py | 37 ++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py index 0df8e90e..1c93c498 100644 --- a/mkdocs/commands/build.py +++ b/mkdocs/commands/build.py @@ -20,7 +20,11 @@ get_files, set_exclusions, ) -from mkdocs.structure.nav import Navigation, get_navigation +from mkdocs.structure.nav import ( + Navigation, + _set_section_titles_from_index_pages, + get_navigation, +) from mkdocs.structure.pages import Page from mkdocs.utils import DuplicateFilter # noqa: F401 - legacy re-export from mkdocs.utils import templates @@ -351,6 +355,9 @@ def build( + "\n - ".join(excluded) ) + # Update auto-generated section titles from index page titles. + _set_section_titles_from_index_pages(nav.items) + # Run `env` plugin events. env = config.plugins.on_env(env, config=config, files=files) diff --git a/mkdocs/structure/nav.py b/mkdocs/structure/nav.py index 3ebe0f47..6d2319f0 100644 --- a/mkdocs/structure/nav.py +++ b/mkdocs/structure/nav.py @@ -265,3 +265,26 @@ def _add_previous_and_next_links(pages: list[Page]) -> None: zipped = zip(bookended[:-2], pages, bookended[2:]) for page0, page1, page2 in zipped: page1.previous_page, page1.next_page = page0, page2 + + +def _set_section_titles_from_index_pages(items: list[StructureItem]) -> None: + """ + For auto-generated navigation, update section titles to use the index + page's title instead of the directory name. + + When `nav` is not explicitly configured, MkDocs generates section names + from directory names (e.g. "about" for an "about/" directory). If the + section contains an index page (e.g. "about/index.md"), this function + uses that page's title as the section title instead. + + This only applies when `page.title` is not None (i.e. the page has + been read/rendered, so its title is known from metadata or headings). + """ + for item in items: + if not item.is_section: + continue + for child in item.children: + if child.is_page and child.is_index and child.title is not None: + item.title = child.title + break + _set_section_titles_from_index_pages(item.children) diff --git a/mkdocs/tests/structure/nav_tests.py b/mkdocs/tests/structure/nav_tests.py index 4f020ded..6b4530c5 100644 --- a/mkdocs/tests/structure/nav_tests.py +++ b/mkdocs/tests/structure/nav_tests.py @@ -4,7 +4,12 @@ import unittest from mkdocs.structure.files import File, Files, set_exclusions -from mkdocs.structure.nav import Section, _get_by_type, get_navigation +from mkdocs.structure.nav import ( + Section, + _get_by_type, + _set_section_titles_from_index_pages, + get_navigation, +) from mkdocs.structure.pages import Page from mkdocs.tests.base import dedent, load_config @@ -608,3 +613,33 @@ def test_get_by_type_nested_sections(self): files = Files(fs) site_navigation = get_navigation(files, cfg) self.assertEqual(len(_get_by_type(site_navigation, Section)), 2) + + def test_smart_section_titles_from_index_pages(self): + """Section titles use index page titles instead of directory names.""" + cfg = load_config(site_url="http://example.com/") + fs = [ + "index.md", + "about/index.md", + "about/license.md", + "api-guide/index.md", + "api-guide/running.md", + ] + files = Files( + [File(s, cfg.docs_dir, cfg.site_dir, cfg.use_directory_urls) for s in fs] + ) + site_navigation = get_navigation(files, cfg) + + # Before update: sections use directory names + about_section = site_navigation.items[1] + api_section = site_navigation.items[2] + self.assertEqual(about_section.title, "About") + self.assertEqual(api_section.title, "Api guide") + + # Set titles on index pages (as if they were read/rendered) + about_section.children[0].title = "About This Project" + api_section.children[0].title = "API Reference" + + _set_section_titles_from_index_pages(site_navigation.items) + + self.assertEqual(about_section.title, "About This Project") + self.assertEqual(api_section.title, "API Reference") From 5a048dbeb67e1df00be49dbb5294e865b2b2bde2 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Wed, 29 Apr 2026 01:55:45 +0300 Subject: [PATCH 2/3] Refactor section title handling to use instance checks for improved clarity --- .github/workflows/ci.yml | 3 --- mkdocs/structure/nav.py | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a90113b..351664f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,9 +114,6 @@ jobs: - name: Run repository checks if: always() run: pre-commit run --all-files --show-diff-on-failure - - name: Check with mypy - if: always() - run: hatch run types:check package: runs-on: ubuntu-latest diff --git a/mkdocs/structure/nav.py b/mkdocs/structure/nav.py index 6d2319f0..a10b8b3b 100644 --- a/mkdocs/structure/nav.py +++ b/mkdocs/structure/nav.py @@ -254,7 +254,7 @@ def _get_by_type(nav, t: type[T]) -> list[T]: def _add_parent_links(nav) -> None: for item in nav: - if item.is_section: + if isinstance(item, Section): for child in item.children: child.parent = item _add_parent_links(item.children) @@ -281,10 +281,10 @@ def _set_section_titles_from_index_pages(items: list[StructureItem]) -> None: been read/rendered, so its title is known from metadata or headings). """ for item in items: - if not item.is_section: + if not isinstance(item, Section): continue for child in item.children: - if child.is_page and child.is_index and child.title is not None: + if isinstance(child, Page) and child.is_index and child.title is not None: item.title = child.title break _set_section_titles_from_index_pages(item.children) From b2969961f5940fe3b14c2bfd4dd2dc053cc362b7 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 May 2026 19:52:37 +0300 Subject: [PATCH 3/3] Restore mypy CI check and add release note for smart section titles --- .github/workflows/ci.yml | 3 +++ docs/about/release-notes.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 351664f3..7a90113b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,9 @@ jobs: - name: Run repository checks if: always() run: pre-commit run --all-files --show-diff-on-failure + - name: Check with mypy + if: always() + run: hatch run types:check package: runs-on: ubuntu-latest diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index dc90f98b..48ad37a5 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -31,6 +31,10 @@ The current members of the MkDocs-NG team. * Fix malformed URLs (e.g., unterminated IPv6 literals) crashing the entire build. #45 * Fix build crash caused by broken (dangling) symlinks in the docs directory. #46 +### Added + +* Auto-generated section titles now use the index page's title instead of the raw directory name when an index page exists. #54 + ### Maintenance * Add concurrency settings to CI workflows so redundant in-progress jobs are cancelled when new pushes occur. #47