[llvm-branch-commits] [clang] [libc] [llvm] [clang][docs] Adopt the furo theme used by llvm and libc (PR #214869)

Reid Kleckner via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Aug 24 20:56:40 PDT 2026


https://github.com/rnk updated https://github.com/llvm/llvm-project/pull/214869

>From be5a3ad34eb6a84a2d5e055af2ece796925c90f2 Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Thu, 6 Aug 2026 21:10:29 +0000
Subject: [PATCH 1/4] [clang][docs] Prototype Furo theme setup

---
 clang/docs/LifetimeSafety.md                  |  16 --
 clang/docs/analyzer/conf.py                   |  44 ++----
 clang/docs/conf.py                            |  26 +---
 libc/docs/conf.py                             |  33 +----
 llvm/docs/_static/copybutton.css              |  39 -----
 llvm/docs/_static/copybutton.js               |  56 -------
 llvm/docs/_static/custom.css                  |  71 ---------
 llvm/docs/conf.py                             |  39 +----
 utils/docs/llvm_sphinx/__init__.py            |  36 ++++-
 .../docs/llvm_sphinx}/_static/copybutton.css  |   0
 .../docs/llvm_sphinx}/_static/copybutton.js   |   2 +-
 .../docs/llvm_sphinx/_static/furo-common.css  | 140 ++++++++++++++++++
 utils/docs/llvm_sphinx/ext/furo.py            |  37 +++++
 13 files changed, 241 insertions(+), 298 deletions(-)
 delete mode 100644 llvm/docs/_static/copybutton.css
 delete mode 100644 llvm/docs/_static/copybutton.js
 delete mode 100644 llvm/docs/_static/custom.css
 rename {libc/docs => utils/docs/llvm_sphinx}/_static/copybutton.css (100%)
 rename {libc/docs => utils/docs/llvm_sphinx}/_static/copybutton.js (98%)
 create mode 100644 utils/docs/llvm_sphinx/_static/furo-common.css
 create mode 100644 utils/docs/llvm_sphinx/ext/furo.py

diff --git a/clang/docs/LifetimeSafety.md b/clang/docs/LifetimeSafety.md
index 5b643feb4224b..dcf206547643d 100644
--- a/clang/docs/LifetimeSafety.md
+++ b/clang/docs/LifetimeSafety.md
@@ -207,22 +207,6 @@ For more details, see [noescape](https://clang.llvm.org/docs/AttributeReference.
 
 ## Checks Performed
 
-```{raw} html
-<style>
-/* Align text to left and add red/green colors */
-table.colored-code-table td, table.colored-code-table th { text-align: left !important; }
-table.colored-code-table td:first-child, table.colored-code-table th:first-child { background-color: #ffeaea !important; }
-table.colored-code-table td:nth-child(2), table.colored-code-table th:nth-child(2) { background-color: #eafaea !important; }
-table.colored-code-table td .highlight, table.colored-code-table td pre { background-color: transparent !important; border: none !important; }
-
-div.bad-code { background-color: #ffeaea !important; padding: 5px; border-left: 4px solid #ff6b6b; text-align: left !important; }
-div.bad-code .highlight, div.bad-code pre { background-color: transparent !important; border: none !important; }
-
-div.good-code { background-color: #eafaea !important; padding: 5px; border-left: 4px solid #51cf66; text-align: left !important; }
-div.good-code .highlight, div.good-code pre { background-color: transparent !important; border: none !important; }
-</style>
-```
-
 ### Use after scope
 
 This check warns when a pointer or reference is used after the stack variable
diff --git a/clang/docs/analyzer/conf.py b/clang/docs/analyzer/conf.py
index 509aa857c4d52..c9ecbec5118f0 100644
--- a/clang/docs/analyzer/conf.py
+++ b/clang/docs/analyzer/conf.py
@@ -14,6 +14,10 @@
 import sys, os
 from datetime import date
 
+from llvm_sphinx import *  # see llvm-project/utils/docs/README.md
+
+globals().update(common_conf(tags, markdown=Markdown.NEVER))
+
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -21,25 +25,13 @@
 
 # -- General configuration -----------------------------------------------------
 
-# If your documentation needs a minimal Sphinx version, state it here.
-# needs_sphinx = '1.0'
-
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ["sphinx.ext.todo", "sphinx.ext.mathjax"]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
-
-# The suffix of source filenames.
-source_suffix = ".rst"
+extensions += ["sphinx.ext.todo", "sphinx.ext.mathjax"]
 
 # The encoding of source files.
 # source_encoding = 'utf-8-sig'
 
-# The master toctree document.
-master_doc = "index"
-
 # General information about the project.
 project = "Clang Static Analyzer"
 copyright = "2013-%d, Analyzer Team" % date.today().year
@@ -87,39 +79,23 @@
 
 # -- Options for HTML output ---------------------------------------------------
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = "haiku"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-# html_theme_options = {}
+configure_furo(
+    globals(),
+    source_directory="clang/docs/analyzer/",
+    html_title="Clang Static Analyzer",
+)
 
 # Add any paths that contain custom themes here, relative to this directory.
 # html_theme_path = []
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-# html_title = None
-
 # A shorter title for the navigation bar.  Default is the same as html_title.
 # html_short_title = None
 
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-# html_logo = None
-
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
 # html_favicon = None
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = []
-
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 # html_last_updated_fmt = '%b %d, %Y'
diff --git a/clang/docs/conf.py b/clang/docs/conf.py
index b80c859d1dbdf..269dc98d2ac59 100644
--- a/clang/docs/conf.py
+++ b/clang/docs/conf.py
@@ -77,39 +77,23 @@
 
 # -- Options for HTML output ---------------------------------------------------
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = "haiku"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-# html_theme_options = {}
+configure_furo(
+    globals(),
+    source_directory="clang/docs/",
+    html_title="Clang",
+)
 
 # Add any paths that contain custom themes here, relative to this directory.
 # html_theme_path = []
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-# html_title = None
-
 # A shorter title for the navigation bar.  Default is the same as html_title.
 # html_short_title = None
 
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-# html_logo = None
-
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
 # html_favicon = None
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = []
-
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 # html_last_updated_fmt = '%b %d, %Y'
diff --git a/libc/docs/conf.py b/libc/docs/conf.py
index 0aec05980b6f0..47d0d6c917a88 100644
--- a/libc/docs/conf.py
+++ b/libc/docs/conf.py
@@ -90,28 +90,16 @@
 
 # -- Options for HTML output ---------------------------------------------------
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-# html_theme = 'haiku'
-html_theme = "furo"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-html_theme_options = {
-    # "Edit this page" links
-    "source_repository": "https://github.com/llvm/llvm-project/",
-    "source_branch": "main",
-    "source_directory": "libc/docs/",
-}
+configure_furo(
+    globals(),
+    source_directory="libc/docs/",
+    html_title="The LLVM C Library",
+    local_static_path=["_static"],
+)
 
 # Add any paths that contain custom themes here, relative to this directory.
 # html_theme_path = []
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "The LLVM C Library"
-
 # A shorter title for the navigation bar.  Default is the same as html_title.
 # html_short_title = None
 
@@ -124,15 +112,6 @@
 # pixels large.
 # html_favicon = None
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
-
-# Custom JS/CSS files
-html_js_files = ["copybutton.js"]
-html_css_files = ["copybutton.css"]
-
 # html_context = {
 #    'css_files': [
 #        '_static/libc.css'
diff --git a/llvm/docs/_static/copybutton.css b/llvm/docs/_static/copybutton.css
deleted file mode 100644
index 6851a1a9dd4b2..0000000000000
--- a/llvm/docs/_static/copybutton.css
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Copy-to-clipboard button styles */
-div.highlight {
-  position: relative;
-}
-
-button.copybutton {
-  position: absolute;
-  top: 0.4em;
-  right: 0.4em;
-  padding: 0.3em 0.4em;
-  background: var(--color-background-secondary, #f0f0f0);
-  border: 1px solid var(--color-foreground-border, #ccc);
-  border-radius: 4px;
-  cursor: pointer;
-  opacity: 0;
-  transition: opacity 0.2s, background 0.15s;
-  line-height: 1;
-  color: var(--color-foreground-secondary, #555);
-}
-
-div.highlight:hover button.copybutton,
-button.copybutton:focus {
-  opacity: 1;
-}
-
-button.copybutton:hover {
-  background: var(--color-background-hover, #e0e0e0);
-}
-
-button.copybutton.copied {
-  color: var(--color-brand-primary, #2980b9);
-  opacity: 1;
-}
-
-button.copybutton svg {
-  display: block;
-  pointer-events: none;
-  fill: currentColor;
-}
diff --git a/llvm/docs/_static/copybutton.js b/llvm/docs/_static/copybutton.js
deleted file mode 100644
index 6103fec62b6f8..0000000000000
--- a/llvm/docs/_static/copybutton.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Simple "copy to clipboard" button for code blocks.
-// Adds a button to each <div class="highlight"> block.
-(function () {
-  "use strict";
-
-  function addCopyButton(block) {
-    var button = document.createElement("button");
-    button.className = "copybutton";
-    button.title = "Copy";
-    button.setAttribute("aria-label", "Copy code to clipboard");
-    button.innerHTML =
-      '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" width="16">' +
-      '<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 ' +
-      "0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 " +
-      "0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" +
-      '"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 ' +
-      "1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 " +
-      ".138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" +
-      '"/></svg>';
-
-    button.addEventListener("click", function () {
-      var code = block.querySelector("pre").innerText;
-      navigator.clipboard.writeText(code).then(
-        function () {
-          button.classList.add("copied");
-          setTimeout(function () {
-            button.classList.remove("copied");
-          }, 2000);
-        },
-        function () {
-          // Fallback for older browsers
-          var ta = document.createElement("textarea");
-          ta.value = code;
-          ta.style.position = "fixed";
-          ta.style.opacity = "0";
-          document.body.appendChild(ta);
-          ta.focus();
-          ta.select();
-          document.execCommand("copy");
-          document.body.removeChild(ta);
-          button.classList.add("copied");
-          setTimeout(function () {
-            button.classList.remove("copied");
-          }, 2000);
-        }
-      );
-    });
-
-    block.style.position = "relative";
-    block.appendChild(button);
-  }
-
-  document.addEventListener("DOMContentLoaded", function () {
-    document.querySelectorAll("div.highlight").forEach(addCopyButton);
-  });
-})();
diff --git a/llvm/docs/_static/custom.css b/llvm/docs/_static/custom.css
deleted file mode 100644
index 91ff8f26bb8c0..0000000000000
--- a/llvm/docs/_static/custom.css
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Enable flexible wide-viewing for Furo theme. */
-:root {
-  /* Remove Furo's default max-width for the content area. */
-  --content-width: none;
-}
-
-/* These settings seem to reasonably balance between readability and content
- * density for wide screens. */
-.page .sidebar-drawer {
-  width: unset;
-}
-.main .toc-drawer {
-  min-width: 15em;
-}
-
-/* Ensure the main content area is allowed to shrink/not-overflow
-   its grid cell, preventing it from pushing the TOC sidebar away. */
-.main, .content {
-  min-width: 0;
-  max-width: 100%;
-
-  /* Remove default width setting from furo's content. */
-  width: unset;
-}
-
-/* Disable small margin added by various UA stylesheets to prevent
- * scrolling artifacts. */
-body {
-  margin: 0;
-}
-
-/* Allow the top-level container to fill the entire browser width. */
-.page {
-  max-width: none;
-}
-
-/* Ensure wide tables and code blocks have their own scrollbars
-   instead of forcing the container to expand beyond the viewport. */
-.content table,
-.content .literal-block,
-.content .highlight {
-  max-width: 100%;
-  overflow-x: auto;
-  display: block;
-}
-
-/* Ensure paragraphs wrap within the content area. */
-.content p {
-  overflow-wrap: break-word;
-}
-
-/* Pygments emits Error tokens that the theme's style renders as
-   red-bordered boxes. Render them like ordinary code text instead. */
-body[data-theme="dark"] .highlight .err,
-.highlight .err {
-    border: none;
-    background-color: transparent;
-    color: inherit;
-}
-
-/* Printing support. */
- at media print {
-    pre {
-        white-space: pre-wrap !important;
-    }
-    .content-icon-container,
-    .skip-to-content,
-    .back-to-top {
-        display: none !important;
-    }
-}
diff --git a/llvm/docs/conf.py b/llvm/docs/conf.py
index f12ba64ec1203..c9cc4dd333700 100644
--- a/llvm/docs/conf.py
+++ b/llvm/docs/conf.py
@@ -75,50 +75,25 @@
 
 # -- Options for HTML output ---------------------------------------------------
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = "furo"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-html_theme_options = {
-    "source_repository": "https://github.com/llvm/llvm-project",
-    "source_branch": "main",
-    "source_directory": "llvm/docs/",
-}
+configure_furo(
+    globals(),
+    source_directory="llvm/docs/",
+    html_title="LLVM",
+    html_logo="_static/LLVMWyvernSmall.png",
+    local_static_path=["_static"],
+)
 
 # Add any paths that contain custom themes here, relative to this directory.
 # html_theme_path = ["_themes"]
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "LLVM"
-
 # A shorter title for the navigation bar.  Default is the same as html_title.
 # html_short_title = None
 
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = "_static/LLVMWyvernSmall.png"
-
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
 # html_favicon = None
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
-
-html_js_files = ["copybutton.js"]
-
-html_css_files = [
-    "copybutton.css",
-    "custom.css",
-]
-
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 html_last_updated_fmt = "%Y-%m-%d"
diff --git a/utils/docs/llvm_sphinx/__init__.py b/utils/docs/llvm_sphinx/__init__.py
index 18aa330dc071b..a97d945611047 100644
--- a/utils/docs/llvm_sphinx/__init__.py
+++ b/utils/docs/llvm_sphinx/__init__.py
@@ -18,7 +18,7 @@
 """
 
 import sys
-from typing import Any, Dict, TYPE_CHECKING
+from typing import Any, Dict, Iterable, Optional
 from enum import Enum, auto
 from sphinx.util.tags import Tags
 from llvm_sphinx.help import venv_help
@@ -65,6 +65,40 @@ def common_conf(tags: Tags, markdown=Markdown.ALWAYS) -> Dict[str, Any]:
     return locals()
 
 
+def _append_unique(target, entries):
+    for entry in entries:
+        if entry not in target:
+            target.append(entry)
+
+
+def configure_furo(
+    conf: Dict[str, Any],
+    *,
+    source_directory: str,
+    html_title: str,
+    html_logo: Optional[str] = None,
+    local_static_path: Iterable[str] = (),
+    extra_css_files: Iterable[str] = (),
+    extra_js_files: Iterable[str] = (),
+) -> None:
+    """Configure the shared Furo theme setup for LLVM Sphinx projects."""
+    extensions = conf.setdefault("extensions", [])
+    _append_unique(extensions, ["llvm_sphinx.ext.furo"])
+
+    conf["html_theme"] = "furo"
+    conf["html_theme_options"] = {
+        "source_repository": "https://github.com/llvm/llvm-project",
+        "source_branch": "main",
+        "source_directory": source_directory,
+    }
+    conf["html_title"] = html_title
+    if html_logo is not None:
+        conf["html_logo"] = html_logo
+    conf["html_static_path"] = list(local_static_path)
+    conf["html_css_files"] = list(extra_css_files)
+    conf["html_js_files"] = list(extra_js_files)
+
+
 # Some of our markdown documentation numbers section titles
 # This helpers is used by myst to remove that numbering from the anchor links.
 def make_slug(s: str) -> str:
diff --git a/libc/docs/_static/copybutton.css b/utils/docs/llvm_sphinx/_static/copybutton.css
similarity index 100%
rename from libc/docs/_static/copybutton.css
rename to utils/docs/llvm_sphinx/_static/copybutton.css
diff --git a/libc/docs/_static/copybutton.js b/utils/docs/llvm_sphinx/_static/copybutton.js
similarity index 98%
rename from libc/docs/_static/copybutton.js
rename to utils/docs/llvm_sphinx/_static/copybutton.js
index 6103fec62b6f8..92f730aca8172 100644
--- a/libc/docs/_static/copybutton.js
+++ b/utils/docs/llvm_sphinx/_static/copybutton.js
@@ -28,7 +28,7 @@
           }, 2000);
         },
         function () {
-          // Fallback for older browsers
+          // Fallback for older browsers.
           var ta = document.createElement("textarea");
           ta.value = code;
           ta.style.position = "fixed";
diff --git a/utils/docs/llvm_sphinx/_static/furo-common.css b/utils/docs/llvm_sphinx/_static/furo-common.css
new file mode 100644
index 0000000000000..26d3da3445de0
--- /dev/null
+++ b/utils/docs/llvm_sphinx/_static/furo-common.css
@@ -0,0 +1,140 @@
+/* Enable flexible wide-viewing for Furo theme. */
+:root {
+  /* Remove Furo's default max-width for the content area. */
+  --content-width: none;
+}
+
+/* These settings seem to reasonably balance between readability and content
+ * density for wide screens. */
+.page .sidebar-drawer {
+  width: unset;
+}
+
+.main .toc-drawer {
+  min-width: 15em;
+}
+
+/* Ensure the main content area is allowed to shrink/not-overflow
+   its grid cell, preventing it from pushing the TOC sidebar away. */
+.main,
+.content {
+  min-width: 0;
+  max-width: 100%;
+
+  /* Remove default width setting from Furo's content. */
+  width: unset;
+}
+
+.main > .content {
+  box-sizing: border-box;
+  flex: 1 1 auto;
+}
+
+/* Disable small margin added by various UA stylesheets to prevent
+ * scrolling artifacts. */
+body {
+  margin: 0;
+}
+
+/* Allow the top-level container to fill the entire browser width. */
+.page {
+  max-width: none;
+}
+
+/* Ensure wide tables and code blocks have their own scrollbars
+   instead of forcing the container to expand beyond the viewport. */
+.content table,
+.content .literal-block,
+.content .highlight {
+  max-width: 100%;
+  overflow-x: auto;
+  display: block;
+}
+
+/* Ensure paragraphs wrap within the content area. */
+.content p {
+  overflow-wrap: break-word;
+}
+
+/* Pygments emits Error tokens that the theme's style renders as
+   red-bordered boxes. Render them like ordinary code text instead. */
+body[data-theme="dark"] .highlight .err,
+.highlight .err {
+  border: none;
+  background-color: transparent;
+  color: inherit;
+}
+
+/* Some pages use red/green comparison tables for bad and corrected code.
+   Keep the surfaces theme-aware so Pygments colors stay readable. */
+body {
+  --llvm-code-comparison-bad-background: #fff1f1;
+  --llvm-code-comparison-bad-border: #d73a49;
+  --llvm-code-comparison-good-background: #eef9ee;
+  --llvm-code-comparison-good-border: #2da44e;
+}
+
+body[data-theme="dark"] {
+  --llvm-code-comparison-bad-background: rgba(248, 81, 73, 0.16);
+  --llvm-code-comparison-bad-border: #f85149;
+  --llvm-code-comparison-good-background: rgba(63, 185, 80, 0.16);
+  --llvm-code-comparison-good-border: #3fb950;
+}
+
+.content table.colored-code-table td,
+.content table.colored-code-table th {
+  text-align: left;
+}
+
+.content table.colored-code-table td:first-child,
+.content table.colored-code-table th:first-child {
+  background-color: var(--llvm-code-comparison-bad-background);
+}
+
+.content table.colored-code-table td:nth-child(2),
+.content table.colored-code-table th:nth-child(2) {
+  background-color: var(--llvm-code-comparison-good-background);
+}
+
+.content table.colored-code-table td .highlight,
+.content table.colored-code-table td pre {
+  background-color: transparent;
+  border: none;
+}
+
+.content div.bad-code,
+.content div.good-code {
+  padding: 5px;
+  text-align: left;
+}
+
+.content div.bad-code {
+  background-color: var(--llvm-code-comparison-bad-background);
+  border-left: 4px solid var(--llvm-code-comparison-bad-border);
+}
+
+.content div.good-code {
+  background-color: var(--llvm-code-comparison-good-background);
+  border-left: 4px solid var(--llvm-code-comparison-good-border);
+}
+
+.content div.bad-code .highlight,
+.content div.bad-code pre,
+.content div.good-code .highlight,
+.content div.good-code pre {
+  background-color: transparent;
+  border: none;
+}
+
+/* Printing support. */
+ at media print {
+  pre {
+    white-space: pre-wrap !important;
+  }
+
+  .content-icon-container,
+  .skip-to-content,
+  .back-to-top {
+    display: none !important;
+  }
+}
diff --git a/utils/docs/llvm_sphinx/ext/furo.py b/utils/docs/llvm_sphinx/ext/furo.py
new file mode 100644
index 0000000000000..13c76fe4865ab
--- /dev/null
+++ b/utils/docs/llvm_sphinx/ext/furo.py
@@ -0,0 +1,37 @@
+"""Shared Furo theme assets for LLVM Sphinx projects."""
+
+from pathlib import Path
+
+from sphinx.util.fileutil import copy_asset
+
+_SHARED_STATIC_PREFIX = "llvm-sphinx"
+_SHARED_STATIC_DIR = Path(__file__).parents[1] / "_static"
+
+
+def _add_shared_static_files(app):
+    if app.builder.format != "html":
+        return
+
+    app.add_js_file(f"{_SHARED_STATIC_PREFIX}/copybutton.js")
+    app.add_css_file(f"{_SHARED_STATIC_PREFIX}/copybutton.css")
+    app.add_css_file(f"{_SHARED_STATIC_PREFIX}/furo-common.css")
+
+
+def _copy_shared_static_files(app, exception):
+    if exception is not None or app.builder.format != "html":
+        return
+
+    copy_asset(
+        str(_SHARED_STATIC_DIR),
+        str(Path(app.builder.outdir) / "_static" / _SHARED_STATIC_PREFIX),
+    )
+
+
+def setup(app):
+    app.connect("builder-inited", _add_shared_static_files)
+    app.connect("build-finished", _copy_shared_static_files)
+    return {
+        "version": "1.0",
+        "parallel_read_safe": True,
+        "parallel_write_safe": True,
+    }

>From cddd02b700765bc39f5f437dd4e12ca0cd08284f Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Mon, 17 Aug 2026 18:00:39 +0000
Subject: [PATCH 2/4] [docs] Restore project-specific Furo CSS

---
 clang/docs/_static/custom.css                 |  60 ++++++++
 clang/docs/analyzer/conf.py                   |   3 +
 clang/docs/conf.py                            |   3 +
 llvm/docs/_static/custom.css                  |  71 +++++++++
 llvm/docs/conf.py                             |   3 +-
 utils/docs/llvm_sphinx/__init__.py            |   8 +
 .../llvm_sphinx}/_static/LLVMWyvernSmall.png  | Bin
 .../docs/llvm_sphinx/_static/furo-common.css  | 140 ------------------
 utils/docs/llvm_sphinx/ext/furo.py            |   1 -
 9 files changed, 147 insertions(+), 142 deletions(-)
 create mode 100644 clang/docs/_static/custom.css
 create mode 100644 llvm/docs/_static/custom.css
 rename {llvm/docs => utils/docs/llvm_sphinx}/_static/LLVMWyvernSmall.png (100%)
 delete mode 100644 utils/docs/llvm_sphinx/_static/furo-common.css

diff --git a/clang/docs/_static/custom.css b/clang/docs/_static/custom.css
new file mode 100644
index 0000000000000..960c1dd5e5f9f
--- /dev/null
+++ b/clang/docs/_static/custom.css
@@ -0,0 +1,60 @@
+/* Some Clang pages use red/green comparison tables for bad and corrected code.
+   Keep the surfaces theme-aware so Pygments colors stay readable. */
+body {
+  --clang-code-comparison-bad-background: #fff1f1;
+  --clang-code-comparison-bad-border: #d73a49;
+  --clang-code-comparison-good-background: #eef9ee;
+  --clang-code-comparison-good-border: #2da44e;
+}
+
+body[data-theme="dark"] {
+  --clang-code-comparison-bad-background: rgba(248, 81, 73, 0.16);
+  --clang-code-comparison-bad-border: #f85149;
+  --clang-code-comparison-good-background: rgba(63, 185, 80, 0.16);
+  --clang-code-comparison-good-border: #3fb950;
+}
+
+.content table.colored-code-table td,
+.content table.colored-code-table th {
+  text-align: left;
+}
+
+.content table.colored-code-table td:first-child,
+.content table.colored-code-table th:first-child {
+  background-color: var(--clang-code-comparison-bad-background);
+}
+
+.content table.colored-code-table td:nth-child(2),
+.content table.colored-code-table th:nth-child(2) {
+  background-color: var(--clang-code-comparison-good-background);
+}
+
+.content table.colored-code-table td .highlight,
+.content table.colored-code-table td pre {
+  background-color: transparent;
+  border: none;
+}
+
+.content div.bad-code,
+.content div.good-code {
+  padding: 5px;
+  text-align: left;
+}
+
+.content div.bad-code {
+  background-color: var(--clang-code-comparison-bad-background);
+  border-left: 4px solid var(--clang-code-comparison-bad-border);
+}
+
+.content div.good-code {
+  background-color: var(--clang-code-comparison-good-background);
+  border-left: 4px solid var(--clang-code-comparison-good-border);
+}
+
+.content div.bad-code .highlight,
+.content div.bad-code pre,
+.content div.good-code .highlight,
+.content div.good-code pre {
+  background-color: transparent;
+  border: none;
+}
diff --git a/clang/docs/analyzer/conf.py b/clang/docs/analyzer/conf.py
index c9ecbec5118f0..38cf375920bde 100644
--- a/clang/docs/analyzer/conf.py
+++ b/clang/docs/analyzer/conf.py
@@ -83,6 +83,9 @@
     globals(),
     source_directory="clang/docs/analyzer/",
     html_title="Clang Static Analyzer",
+    html_logo=shared_static_asset("LLVMWyvernSmall.png"),
+    local_static_path=["../_static"],
+    extra_css_files=["custom.css"],
 )
 
 # Add any paths that contain custom themes here, relative to this directory.
diff --git a/clang/docs/conf.py b/clang/docs/conf.py
index 269dc98d2ac59..898fd0ec5d513 100644
--- a/clang/docs/conf.py
+++ b/clang/docs/conf.py
@@ -81,6 +81,9 @@
     globals(),
     source_directory="clang/docs/",
     html_title="Clang",
+    html_logo=shared_static_asset("LLVMWyvernSmall.png"),
+    local_static_path=["_static"],
+    extra_css_files=["custom.css"],
 )
 
 # Add any paths that contain custom themes here, relative to this directory.
diff --git a/llvm/docs/_static/custom.css b/llvm/docs/_static/custom.css
new file mode 100644
index 0000000000000..91ff8f26bb8c0
--- /dev/null
+++ b/llvm/docs/_static/custom.css
@@ -0,0 +1,71 @@
+/* Enable flexible wide-viewing for Furo theme. */
+:root {
+  /* Remove Furo's default max-width for the content area. */
+  --content-width: none;
+}
+
+/* These settings seem to reasonably balance between readability and content
+ * density for wide screens. */
+.page .sidebar-drawer {
+  width: unset;
+}
+.main .toc-drawer {
+  min-width: 15em;
+}
+
+/* Ensure the main content area is allowed to shrink/not-overflow
+   its grid cell, preventing it from pushing the TOC sidebar away. */
+.main, .content {
+  min-width: 0;
+  max-width: 100%;
+
+  /* Remove default width setting from furo's content. */
+  width: unset;
+}
+
+/* Disable small margin added by various UA stylesheets to prevent
+ * scrolling artifacts. */
+body {
+  margin: 0;
+}
+
+/* Allow the top-level container to fill the entire browser width. */
+.page {
+  max-width: none;
+}
+
+/* Ensure wide tables and code blocks have their own scrollbars
+   instead of forcing the container to expand beyond the viewport. */
+.content table,
+.content .literal-block,
+.content .highlight {
+  max-width: 100%;
+  overflow-x: auto;
+  display: block;
+}
+
+/* Ensure paragraphs wrap within the content area. */
+.content p {
+  overflow-wrap: break-word;
+}
+
+/* Pygments emits Error tokens that the theme's style renders as
+   red-bordered boxes. Render them like ordinary code text instead. */
+body[data-theme="dark"] .highlight .err,
+.highlight .err {
+    border: none;
+    background-color: transparent;
+    color: inherit;
+}
+
+/* Printing support. */
+ at media print {
+    pre {
+        white-space: pre-wrap !important;
+    }
+    .content-icon-container,
+    .skip-to-content,
+    .back-to-top {
+        display: none !important;
+    }
+}
diff --git a/llvm/docs/conf.py b/llvm/docs/conf.py
index c9cc4dd333700..0a618b2643e0a 100644
--- a/llvm/docs/conf.py
+++ b/llvm/docs/conf.py
@@ -79,8 +79,9 @@
     globals(),
     source_directory="llvm/docs/",
     html_title="LLVM",
-    html_logo="_static/LLVMWyvernSmall.png",
+    html_logo=shared_static_asset("LLVMWyvernSmall.png"),
     local_static_path=["_static"],
+    extra_css_files=["custom.css"],
 )
 
 # Add any paths that contain custom themes here, relative to this directory.
diff --git a/utils/docs/llvm_sphinx/__init__.py b/utils/docs/llvm_sphinx/__init__.py
index a97d945611047..2693218931b43 100644
--- a/utils/docs/llvm_sphinx/__init__.py
+++ b/utils/docs/llvm_sphinx/__init__.py
@@ -18,11 +18,14 @@
 """
 
 import sys
+from pathlib import Path
 from typing import Any, Dict, Iterable, Optional
 from enum import Enum, auto
 from sphinx.util.tags import Tags
 from llvm_sphinx.help import venv_help
 
+_SHARED_STATIC_DIR = Path(__file__).parent / "_static"
+
 
 class Markdown(Enum):
     ALWAYS = auto()
@@ -99,6 +102,11 @@ def configure_furo(
     conf["html_js_files"] = list(extra_js_files)
 
 
+def shared_static_asset(filename: str) -> str:
+    """Return an absolute path to a shared LLVM Sphinx static asset."""
+    return str(_SHARED_STATIC_DIR / filename)
+
+
 # Some of our markdown documentation numbers section titles
 # This helpers is used by myst to remove that numbering from the anchor links.
 def make_slug(s: str) -> str:
diff --git a/llvm/docs/_static/LLVMWyvernSmall.png b/utils/docs/llvm_sphinx/_static/LLVMWyvernSmall.png
similarity index 100%
rename from llvm/docs/_static/LLVMWyvernSmall.png
rename to utils/docs/llvm_sphinx/_static/LLVMWyvernSmall.png
diff --git a/utils/docs/llvm_sphinx/_static/furo-common.css b/utils/docs/llvm_sphinx/_static/furo-common.css
deleted file mode 100644
index 26d3da3445de0..0000000000000
--- a/utils/docs/llvm_sphinx/_static/furo-common.css
+++ /dev/null
@@ -1,140 +0,0 @@
-/* Enable flexible wide-viewing for Furo theme. */
-:root {
-  /* Remove Furo's default max-width for the content area. */
-  --content-width: none;
-}
-
-/* These settings seem to reasonably balance between readability and content
- * density for wide screens. */
-.page .sidebar-drawer {
-  width: unset;
-}
-
-.main .toc-drawer {
-  min-width: 15em;
-}
-
-/* Ensure the main content area is allowed to shrink/not-overflow
-   its grid cell, preventing it from pushing the TOC sidebar away. */
-.main,
-.content {
-  min-width: 0;
-  max-width: 100%;
-
-  /* Remove default width setting from Furo's content. */
-  width: unset;
-}
-
-.main > .content {
-  box-sizing: border-box;
-  flex: 1 1 auto;
-}
-
-/* Disable small margin added by various UA stylesheets to prevent
- * scrolling artifacts. */
-body {
-  margin: 0;
-}
-
-/* Allow the top-level container to fill the entire browser width. */
-.page {
-  max-width: none;
-}
-
-/* Ensure wide tables and code blocks have their own scrollbars
-   instead of forcing the container to expand beyond the viewport. */
-.content table,
-.content .literal-block,
-.content .highlight {
-  max-width: 100%;
-  overflow-x: auto;
-  display: block;
-}
-
-/* Ensure paragraphs wrap within the content area. */
-.content p {
-  overflow-wrap: break-word;
-}
-
-/* Pygments emits Error tokens that the theme's style renders as
-   red-bordered boxes. Render them like ordinary code text instead. */
-body[data-theme="dark"] .highlight .err,
-.highlight .err {
-  border: none;
-  background-color: transparent;
-  color: inherit;
-}
-
-/* Some pages use red/green comparison tables for bad and corrected code.
-   Keep the surfaces theme-aware so Pygments colors stay readable. */
-body {
-  --llvm-code-comparison-bad-background: #fff1f1;
-  --llvm-code-comparison-bad-border: #d73a49;
-  --llvm-code-comparison-good-background: #eef9ee;
-  --llvm-code-comparison-good-border: #2da44e;
-}
-
-body[data-theme="dark"] {
-  --llvm-code-comparison-bad-background: rgba(248, 81, 73, 0.16);
-  --llvm-code-comparison-bad-border: #f85149;
-  --llvm-code-comparison-good-background: rgba(63, 185, 80, 0.16);
-  --llvm-code-comparison-good-border: #3fb950;
-}
-
-.content table.colored-code-table td,
-.content table.colored-code-table th {
-  text-align: left;
-}
-
-.content table.colored-code-table td:first-child,
-.content table.colored-code-table th:first-child {
-  background-color: var(--llvm-code-comparison-bad-background);
-}
-
-.content table.colored-code-table td:nth-child(2),
-.content table.colored-code-table th:nth-child(2) {
-  background-color: var(--llvm-code-comparison-good-background);
-}
-
-.content table.colored-code-table td .highlight,
-.content table.colored-code-table td pre {
-  background-color: transparent;
-  border: none;
-}
-
-.content div.bad-code,
-.content div.good-code {
-  padding: 5px;
-  text-align: left;
-}
-
-.content div.bad-code {
-  background-color: var(--llvm-code-comparison-bad-background);
-  border-left: 4px solid var(--llvm-code-comparison-bad-border);
-}
-
-.content div.good-code {
-  background-color: var(--llvm-code-comparison-good-background);
-  border-left: 4px solid var(--llvm-code-comparison-good-border);
-}
-
-.content div.bad-code .highlight,
-.content div.bad-code pre,
-.content div.good-code .highlight,
-.content div.good-code pre {
-  background-color: transparent;
-  border: none;
-}
-
-/* Printing support. */
- at media print {
-  pre {
-    white-space: pre-wrap !important;
-  }
-
-  .content-icon-container,
-  .skip-to-content,
-  .back-to-top {
-    display: none !important;
-  }
-}
diff --git a/utils/docs/llvm_sphinx/ext/furo.py b/utils/docs/llvm_sphinx/ext/furo.py
index 13c76fe4865ab..7bb876c448daa 100644
--- a/utils/docs/llvm_sphinx/ext/furo.py
+++ b/utils/docs/llvm_sphinx/ext/furo.py
@@ -14,7 +14,6 @@ def _add_shared_static_files(app):
 
     app.add_js_file(f"{_SHARED_STATIC_PREFIX}/copybutton.js")
     app.add_css_file(f"{_SHARED_STATIC_PREFIX}/copybutton.css")
-    app.add_css_file(f"{_SHARED_STATIC_PREFIX}/furo-common.css")
 
 
 def _copy_shared_static_files(app, exception):

>From 6fcd8fe78fce2a53cd84392ca2020696ab4753db Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Tue, 18 Aug 2026 02:52:28 +0000
Subject: [PATCH 3/4] Fix text color in dark mode

---
 clang/docs/UsersManual.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/docs/UsersManual.md b/clang/docs/UsersManual.md
index a6c11683f5395..e8070277dd958 100644
--- a/clang/docs/UsersManual.md
+++ b/clang/docs/UsersManual.md
@@ -275,7 +275,7 @@ specific parts of the diagnostic, e.g.,
 
 ::::{raw} html
 <pre>
-  <b><span style="color:black">test.c:28:8: <span style="color:magenta">warning</span>: extra tokens at end of #endif directive [-Wextra-tokens]</span></b>
+  <b>test.c:28:8: <span style="color:magenta">warning</span>: extra tokens at end of #endif directive [-Wextra-tokens]</b>
   #endif bad
          <span style="color:green">^</span>
          <span style="color:green">//</span>

>From 3954d3421568ccedec3b8fa2b3a04395c4761ad9 Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Tue, 25 Aug 2026 02:42:52 +0000
Subject: [PATCH 4/4] [clang][docs] Give DataFlow figures a light background

---
 clang/docs/DataFlowAnalysisIntro.md | 35 +++++++++++++++++++++++------
 clang/docs/_static/custom.css       |  4 ++++
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/clang/docs/DataFlowAnalysisIntro.md b/clang/docs/DataFlowAnalysisIntro.md
index 67faae0cd9e72..8a8a6cb290f66 100644
--- a/clang/docs/DataFlowAnalysisIntro.md
+++ b/clang/docs/DataFlowAnalysisIntro.md
@@ -82,7 +82,10 @@ inclusion relation as ordering and set union as a join.
 Lattices are often represented visually as Hasse diagrams. Here is a Hasse
 diagram for our lattice that tracks subsets of integers:
 
-![Hasse diagram for a lattice of integer sets](DataFlowAnalysisIntroImages/IntegerSetsInfiniteLattice.svg)
+:::{figure} DataFlowAnalysisIntroImages/IntegerSetsInfiniteLattice.svg
+:class: light-background
+:alt: Hasse diagram for a lattice of integer sets
+:::
 
 Computing the join in the lattice corresponds to finding the lowest common
 ancestor (LCA) between two nodes in its Hasse diagram. There is a vast amount of
@@ -176,7 +179,10 @@ lattice that we can use in practice to track possible values of integer
 variables. This lattice represents sets of integers with 1, 2, or 3 elements, as
 well as top and bottom. Here is a Hasse diagram for it:
 
-![Hasse diagram for a lattice of integer sets](DataFlowAnalysisIntroImages/IntegerSetsFiniteLattice.svg)
+:::{figure} DataFlowAnalysisIntroImages/IntegerSetsFiniteLattice.svg
+:class: light-background
+:alt: Hasse diagram for a lattice of integer sets
+:::
 
 ### Formalization
 
@@ -204,7 +210,10 @@ As humans, we understand the control flow from the program text. We used our
 understanding of control flow to find program points where two flows join.
 Formally, control flow is represented by a CFG (control flow graph):
 
-![CFG for the code above](DataFlowAnalysisIntroImages/CFGExample.svg)
+:::{figure} DataFlowAnalysisIntroImages/CFGExample.svg
+:class: light-background
+:alt: CFG for the code above
+:::
 
 We can compute sets of possible values by propagating them through the CFG of
 the function:
@@ -277,7 +286,10 @@ conclusion is called a postcondition.
 
 In terms of the CFG, we join the information from all predecessor basic blocks.
 
-![Modeling the effects of a CFG basic block](DataFlowAnalysisIntroImages/CFGJoinRule.svg)
+:::{figure} DataFlowAnalysisIntroImages/CFGJoinRule.svg
+:class: light-background
+:alt: Modeling the effects of a CFG basic block
+:::
 
 Putting it all together, to model the effects of a basic block we compute:
 
@@ -515,7 +527,10 @@ between normal states is determined by reversed inclusion relation on the set of
 overwritten parameter's member fields (lattice's `⩽` is `⊇` on the set of
 overwritten fields).
 
-![Lattice for data flow analysis that identifies output parameters](DataFlowAnalysisIntroImages/OutputParameterIdentificationLattice.svg)
+:::{figure} DataFlowAnalysisIntroImages/OutputParameterIdentificationLattice.svg
+:class: light-background
+:alt: Lattice for data flow analysis that identifies output parameters
+:::
 
 To determine whether a statement reads or writes a field we can implement
 symbolic evaluation of `DeclRefExpr`s, `LValueToRValue` casts, pointer
@@ -636,7 +651,10 @@ For this purpose we can use lattice in a form of a mapping from variable
 declarations to initialization states; each initialization state is represented
 by the following lattice:
 
-![Lattice for definitive initialization analysis](DataFlowAnalysisIntroImages/DefinitiveInitializationLattice.svg)
+:::{figure} DataFlowAnalysisIntroImages/DefinitiveInitializationLattice.svg
+:class: light-background
+:alt: Lattice for definitive initialization analysis
+:::
 
 A lattice element could also capture the source locations of the branches that
 lead us to the corresponding program point. Diagnostics would use this
@@ -682,7 +700,10 @@ void UniqueOwnership1() {
 This problem can be solved with a lattice in form of map from value declarations
 to pointer states:
 
-![Lattice that identifies candidates for unique_ptr refactoring](DataFlowAnalysisIntroImages/UniquePtrLattice.svg)
+:::{figure} DataFlowAnalysisIntroImages/UniquePtrLattice.svg
+:class: light-background
+:alt: Lattice that identifies candidates for unique_ptr refactoring
+:::
 
 We can perform the refactoring if at the exit of a function `pi` is
 `Compatible`.
diff --git a/clang/docs/_static/custom.css b/clang/docs/_static/custom.css
index 960c1dd5e5f9f..10831fd2879b4 100644
--- a/clang/docs/_static/custom.css
+++ b/clang/docs/_static/custom.css
@@ -14,6 +14,10 @@ body[data-theme="dark"] {
   --clang-code-comparison-good-border: #3fb950;
 }
 
+.content img.light-background {
+  background-color: #fff;
+}
+
 .content table.colored-code-table td,
 .content table.colored-code-table th {
   text-align: left;



More information about the llvm-branch-commits mailing list