diff --git a/.dockerignore b/.dockerignore
index df08c066ce3b4..ce4bd263ec307 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -73,6 +73,7 @@
!conf.py
!docs
!docker-stack-docs
+!docs-theme
!providers-summary-docs
# Avoid triggering context change on README change (new companies using Airflow)
diff --git a/.github/workflows/airflow-distributions-tests.yml b/.github/workflows/airflow-distributions-tests.yml
index 53692b5d87350..e5b2f42033802 100644
--- a/.github/workflows/airflow-distributions-tests.yml
+++ b/.github/workflows/airflow-distributions-tests.yml
@@ -104,6 +104,11 @@ jobs:
- name: "Install Breeze"
uses: ./.github/actions/breeze
if: ${{ inputs.use-local-venv == 'true' }}
+ # `_gen/` is gitignored and bind-mounted from host into the container, so
+ # baking it into the image is masked at runtime. Fetch on host instead.
+ - name: "Fetch sphinx-airflow-theme static assets"
+ shell: bash
+ run: python scripts/ci/fetch_theme_assets.py
- name: "Cleanup dist files"
run: rm -fv ./dist/*
if: ${{ matrix.python-version == inputs.default-python-version }}
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 7cc05b2c053a3..6cb3de63bf993 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -1882,6 +1882,8 @@ RUN --mount=type=cache,id=ci-$TARGETARCH-$DEPENDENCY_CACHE_EPOCH,target=/root/.c
bash /scripts/docker/install_additional_dependencies.sh; \
fi
+RUN python "${AIRFLOW_SOURCES}/scripts/ci/fetch_theme_assets.py"
+
COPY --from=scripts entrypoint_ci.sh /entrypoint
COPY --from=scripts entrypoint_exec.sh /entrypoint-exec
RUN chmod a+x /entrypoint /entrypoint-exec
diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
index afb22da6355fe..2b65f77d8c1e0 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
@@ -21,6 +21,7 @@
import re
import shlex
import shutil
+import subprocess
import sys
import threading
from pathlib import Path
@@ -751,6 +752,20 @@ def start_airflow(
sys.exit(result.returncode)
+def _ensure_theme_assets() -> None:
+ gen_dir = AIRFLOW_ROOT_PATH / "docs-theme" / "sphinx_airflow_theme" / "static" / "_gen"
+ if gen_dir.is_dir():
+ return
+ console_print("[info]Theme static assets missing — fetching from published wheel...")
+ fetch_script = AIRFLOW_ROOT_PATH / "scripts" / "ci" / "fetch_theme_assets.py"
+ result = subprocess.run([sys.executable, str(fetch_script)], check=False)
+ if result.returncode != 0:
+ console_print(
+ "[error]Failed to fetch theme assets. Run manually: python scripts/ci/fetch_theme_assets.py"
+ )
+ sys.exit(1)
+
+
@main.command(name="build-docs")
@option_builder
@click.option(
@@ -830,6 +845,7 @@ def build_docs(
Build documents.
"""
perform_environment_checks()
+ _ensure_theme_assets()
fix_ownership_using_docker()
cleanup_python_generated_files()
build_params = BuildCiParams(
diff --git a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
index c955459612873..97c78f5e07fa8 100644
--- a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
@@ -97,6 +97,7 @@
("docker-stack-docs", "/opt/airflow/docker-stack-docs"),
("docker-tests", "/opt/airflow/docker-tests"),
("docs", "/opt/airflow/docs"),
+ ("docs-theme", "/opt/airflow/docs-theme"),
("generated", "/opt/airflow/generated"),
("go-sdk", "/opt/airflow/go-sdk"),
("helm-tests", "/opt/airflow/helm-tests"),
diff --git a/dev/breeze/tests/test_theme_workspace.py b/dev/breeze/tests/test_theme_workspace.py
new file mode 100644
index 0000000000000..1eafc82892d85
--- /dev/null
+++ b/dev/breeze/tests/test_theme_workspace.py
@@ -0,0 +1,114 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import contextlib
+import sys
+from pathlib import Path
+from unittest import mock
+from urllib.error import URLError
+
+import pytest
+
+try:
+ import tomllib
+except ModuleNotFoundError:
+ import tomli as tomllib # type: ignore[no-redef]
+
+AIRFLOW_ROOT = Path(__file__).resolve().parents[3]
+sys.path.insert(0, str(AIRFLOW_ROOT / "scripts" / "ci"))
+import fetch_theme_assets # noqa: E402
+
+
+class TestSphinxThemeWorkspace:
+ def test_theme_is_workspace_member(self):
+ with open(AIRFLOW_ROOT / "pyproject.toml", "rb") as f:
+ root_config = tomllib.load(f)
+ members = root_config["tool"]["uv"]["workspace"]["members"]
+ assert "docs-theme" in members
+
+ def test_theme_source_exists(self):
+ theme_dir = AIRFLOW_ROOT / "docs-theme" / "sphinx_airflow_theme"
+ assert theme_dir.is_dir()
+ assert (theme_dir / "__init__.py").is_file()
+ assert (theme_dir / "theme.conf").is_file()
+ assert (theme_dir / "layout.html").is_file()
+
+ def test_theme_has_valid_pyproject(self):
+ with open(AIRFLOW_ROOT / "docs-theme" / "pyproject.toml", "rb") as f:
+ config = tomllib.load(f)
+ assert config["project"]["name"] == "sphinx_airflow_theme"
+ assert config["build-system"]["build-backend"] == "flit_core.buildapi"
+ entry_points = config["project"]["entry-points"]["sphinx.html_themes"]
+ assert "sphinx_airflow_theme" in entry_points
+
+ def test_devel_common_uses_workspace_dep(self):
+ with open(AIRFLOW_ROOT / "devel-common" / "pyproject.toml", "rb") as f:
+ config = tomllib.load(f)
+ docs_deps = config["project"]["optional-dependencies"]["docs"]
+ theme_deps = [d for d in docs_deps if "sphinx-airflow-theme" in d.lower()]
+ assert len(theme_deps) == 1, f"Expected exactly one theme dep, got: {theme_deps}"
+ assert "@https://" not in theme_deps[0], (
+ f"Theme should be a workspace dep, not a URL: {theme_deps[0]}"
+ )
+
+ def test_theme_registered_in_uv_sources(self):
+ with open(AIRFLOW_ROOT / "pyproject.toml", "rb") as f:
+ root_config = tomllib.load(f)
+ sources = root_config["tool"]["uv"]["sources"]
+ assert "sphinx-airflow-theme" in sources
+ assert sources["sphinx-airflow-theme"] == {"workspace": True}
+
+ def test_gen_dir_is_gitignored(self):
+ gitignore = (AIRFLOW_ROOT / "docs-theme" / ".gitignore").read_text()
+ assert "static/_gen/" in gitignore
+
+ def test_fetch_script_exists(self):
+ fetch_script = AIRFLOW_ROOT / "scripts" / "ci" / "fetch_theme_assets.py"
+ assert fetch_script.is_file()
+
+
+class TestFetchThemeRetry:
+ @mock.patch("fetch_theme_assets.time.sleep")
+ @mock.patch("fetch_theme_assets.urlopen")
+ def test_retries_on_transient_failure(self, mock_urlopen, mock_sleep, tmp_path):
+ mock_urlopen.side_effect = [
+ URLError("Connection timed out"),
+ URLError("Connection reset"),
+ mock.MagicMock(read=lambda: (tmp_path / "dummy.whl").write_bytes(b"") or b"PK\x03\x04"),
+ ]
+ with mock.patch.object(fetch_theme_assets, "WHEEL_URL", "https://example.com/{version}.whl"):
+ with mock.patch.object(fetch_theme_assets, "THEME_DIR", tmp_path / "theme"):
+ with mock.patch.object(fetch_theme_assets, "GEN_DIR", tmp_path / "gen"):
+ with mock.patch.object(
+ fetch_theme_assets, "VERSION_STAMP", tmp_path / "gen" / ".version"
+ ):
+ # Will fail on zipfile parse, but we only care that retry logic reached attempt 3
+ with contextlib.suppress(Exception):
+ fetch_theme_assets.fetch_and_extract("0.0.1")
+ assert mock_urlopen.call_count == 3
+ assert mock_sleep.call_count == 2
+ mock_sleep.assert_any_call(2)
+ mock_sleep.assert_any_call(4)
+
+ @mock.patch("fetch_theme_assets.time.sleep")
+ @mock.patch("fetch_theme_assets.urlopen")
+ def test_exits_after_max_retries(self, mock_urlopen, mock_sleep):
+ mock_urlopen.side_effect = URLError("Connection refused")
+ with pytest.raises(SystemExit, match="1"):
+ fetch_theme_assets.fetch_and_extract("0.0.1")
+ assert mock_urlopen.call_count == 3
diff --git a/devel-common/pyproject.toml b/devel-common/pyproject.toml
index 1e7db399cb2ab..76a29e81009f3 100644
--- a/devel-common/pyproject.toml
+++ b/devel-common/pyproject.toml
@@ -81,7 +81,7 @@ dependencies = [
"docutils>=0.21",
"pagefind>=1.5.0",
"pagefind-bin>=1.5.0",
- "sphinx-airflow-theme@https://airflow.apache.org/sphinx-airflow-theme/sphinx_airflow_theme-0.3.9-py3-none-any.whl",
+ "sphinx-airflow-theme>=0.3.9",
"sphinx-argparse>=0.4.0",
"sphinx-autoapi>=3.8.0",
"sphinx-autobuild>=2024.10.2",
diff --git a/docs-theme/.gitignore b/docs-theme/.gitignore
new file mode 100644
index 0000000000000..3df8fd49646f2
--- /dev/null
+++ b/docs-theme/.gitignore
@@ -0,0 +1,2 @@
+*.iml
+sphinx_airflow_theme/static/_gen/
diff --git a/docs-theme/pyproject.toml b/docs-theme/pyproject.toml
new file mode 100644
index 0000000000000..6fe199aadde34
--- /dev/null
+++ b/docs-theme/pyproject.toml
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[build-system]
+requires = ["flit_core==3.12.0"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "sphinx_airflow_theme"
+description = "Airflow theme for Sphinx"
+license = {text = "Apache License 2.0"}
+authors = [
+ {name = "Apache Software Foundation", email = "dev@airflow.apache.org"}
+]
+dynamic = ["version"]
+classifiers = [
+ "Framework :: Sphinx",
+ "Framework :: Sphinx :: Theme",
+ "License :: OSI Approved :: Apache Software License",
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Intended Audience :: Developers",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Operating System :: OS Independent",
+ "Topic :: Documentation",
+ "Topic :: Software Development :: Documentation"
+]
+dependencies = [
+ "sphinx>=7"
+]
+requires-python = ">=3.9"
+
+[project.optional-dependencies]
+dev = []
+
+[project.entry-points."sphinx.html_themes"]
+sphinx_airflow_theme = "sphinx_airflow_theme"
+
+[tool.flit.module]
+name = "sphinx_airflow_theme"
+
+[tool.flit.sdist]
+include = [
+ "sphinx_airflow_theme/**",
+]
diff --git a/docs-theme/sphinx_airflow_theme/__init__.py b/docs-theme/sphinx_airflow_theme/__init__.py
new file mode 100644
index 0000000000000..b8e9aa887c454
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/__init__.py
@@ -0,0 +1,57 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from os import path
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from sphinx.application import Sphinx
+
+__version__ = "0.3.9"
+__version_full__ = __version__
+
+_THEME_DIR = path.abspath(path.dirname(__file__))
+_GEN_DIR = path.join(_THEME_DIR, "static", "_gen")
+
+
+def get_html_theme_path():
+ """Return list of HTML theme paths."""
+ return path.abspath(path.dirname(_THEME_DIR))
+
+
+def setup_my_func(app, config):
+ config.html_theme_options.setdefault(
+ "navbar_links",
+ [
+ {"href": "/index.html", "text": "Documentation"},
+ {"href": "/registry/", "text": "Registry"},
+ ],
+ )
+
+
+def setup(app: Sphinx):
+ if not path.isdir(_GEN_DIR):
+ raise FileNotFoundError(
+ f"Theme static assets not found at {_GEN_DIR}. Run: python scripts/ci/fetch_theme_assets.py"
+ )
+ app.add_html_theme("sphinx_airflow_theme", _THEME_DIR)
+ app.add_css_file("_gen/css/main-custom.min.css")
+ app.add_js_file("js/globaltoc.js")
+ app.connect("config-inited", setup_my_func)
+ return {"version": __version__, "parallel_read_safe": True, "parallel_write_safe": True}
diff --git a/docs-theme/sphinx_airflow_theme/breadcrumbs.html b/docs-theme/sphinx_airflow_theme/breadcrumbs.html
new file mode 100644
index 0000000000000..7e211fdfda497
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/breadcrumbs.html
@@ -0,0 +1,41 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+{% if meta is defined and meta is not none %}
+ {% set check_meta = True %}
+{% else %}
+ {% set check_meta = False %}
+{% endif %}
+
+{% if check_meta and 'github_url' in meta %}
+ {% set display_github = True %}
+{% endif %}
+
+
diff --git a/docs-theme/sphinx_airflow_theme/footer.html b/docs-theme/sphinx_airflow_theme/footer.html
new file mode 100644
index 0000000000000..abb44ea200129
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/footer.html
@@ -0,0 +1,180 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+
+
+
diff --git a/docs-theme/sphinx_airflow_theme/globaltoc.html b/docs-theme/sphinx_airflow_theme/globaltoc.html
new file mode 100644
index 0000000000000..d4426f62d9712
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/globaltoc.html
@@ -0,0 +1,57 @@
+
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+
+
+
+
diff --git a/docs-theme/sphinx_airflow_theme/header.html b/docs-theme/sphinx_airflow_theme/header.html
new file mode 100644
index 0000000000000..e3dedcda13a77
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/header.html
@@ -0,0 +1,142 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+
+
+
+
diff --git a/docs-theme/sphinx_airflow_theme/layout.html b/docs-theme/sphinx_airflow_theme/layout.html
new file mode 100644
index 0000000000000..a4ca32872ffe9
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/layout.html
@@ -0,0 +1,446 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+{#
+ basic/layout.html
+ ~~~~~~~~~~~~~~~~~
+
+ Main layout template for Sphinx themes.
+
+ :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+#}
+{%- set url_root = pathto('', 1) %}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+ {%- set titlesuffix = " — " + docstitle|e %}
+{%- else %}
+ {%- set titlesuffix = "" %}
+{%- endif %}
+{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
+
+
+
+
+
+{%- macro pager() %}
+
+ {%- block rating %}{{ rating() }}{% endblock %}
+ {# END CONTENT #}
+
+ {# END BODY #}
+
+ {# START SIDEBAR #}
+ {{ sidetoc() }}
+ {# END SIDEBAR #}
+
+ {% include "suggest_change_button.html" %}
+
+
+{%- endblock %}
+
+{#{%- block relbar2 %}{{ relbar() }}{% endblock %}#}
+
+{%- block footer %}
+ {% include "footer.html" %}
+{%- endblock %}
+{%- block scripts %}
+ {{- script() }}
+{%- endblock %}
+
+
diff --git a/docs-theme/sphinx_airflow_theme/searchbox.html b/docs-theme/sphinx_airflow_theme/searchbox.html
new file mode 100644
index 0000000000000..8a82c9789c619
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/searchbox.html
@@ -0,0 +1,47 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+
+
+
+
+
+
diff --git a/docs-theme/sphinx_airflow_theme/searchresults.html b/docs-theme/sphinx_airflow_theme/searchresults.html
new file mode 100644
index 0000000000000..6387f64493f1a
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/searchresults.html
@@ -0,0 +1,58 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+
{{ _('Search') }}
+
+ From here you can search these documents. Enter your search
+ words into the box below and click "search".
+
+
+
+
+{%- if search_performed %}
+
{{ _('Search Results') }}
+ {%- if not search_results %}
+
{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}
+ {%- endif %}
+{%- endif %}
+
+ {%- if search_results %}
+
+ {% for href, caption, context in search_results %}
+
diff --git a/docs-theme/sphinx_airflow_theme/static/js/globaltoc.js b/docs-theme/sphinx_airflow_theme/static/js/globaltoc.js
new file mode 100644
index 0000000000000..f71b5fd21f692
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/static/js/globaltoc.js
@@ -0,0 +1,43 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// The script adds a 'click' eventListener to the 'span.toctree-expand' element that makes the active submenus collapsible.
+// To make the submenus collapsible is sufficient to add/remove the 'current' class to the active 'li.toctree-l1' element.
+$("span.toctree-expand").on("click", function() {
+ $(this).closest("li").toggleClass("current");
+});
diff --git a/docs-theme/sphinx_airflow_theme/suggest_change_button.html b/docs-theme/sphinx_airflow_theme/suggest_change_button.html
new file mode 100644
index 0000000000000..22671dc9677a2
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/suggest_change_button.html
@@ -0,0 +1,82 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+{% if meta is defined and meta is not none %}
+ {% set check_meta = True %}
+{% else %}
+ {% set check_meta = False %}
+{% endif %}
+
+{% if check_meta and 'github_url' in meta %}
+ {% set display_github = True %}
+{% endif %}
+
+{% if check_meta and 'github_url' in meta %}
+ {% set display_github = True %}
+{% endif %}
+
+{% if hasdoc(pagename) %}
+ {% if display_github %}
+ {% if check_meta and 'github_url' in meta %}
+ {% set github_link = meta['github_url'] %}
+ {% else %}
+ {% set github_link = 'https://' ~ github_host|default("github.com") ~ '/' ~ github_user ~ '/' ~ github_repo ~ '/' ~ theme_vcs_pageview_mode|default("blob") ~ '/' ~ github_version ~ conf_py_path ~ pagename ~ suffix %}
+ {% endif %}
+
+ {% endif %}
+{% endif %}
diff --git a/docs-theme/sphinx_airflow_theme/theme.conf b/docs-theme/sphinx_airflow_theme/theme.conf
new file mode 100644
index 0000000000000..b73b8ab81a202
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/theme.conf
@@ -0,0 +1,30 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[theme]
+inherit = basic
+stylesheet = _gen/css/main.min.css
+pygments_style = default
+
+[options]
+canonical_url =
+analytics_id =
+# Default set by python code, need to list this here to avoid warning from Sphinx
+navbar_links =
+hide_website_buttons = false
+sidebar_collapse = true
+sidebar_includehidden = true
diff --git a/docs-theme/sphinx_airflow_theme/version-selector.html b/docs-theme/sphinx_airflow_theme/version-selector.html
new file mode 100644
index 0000000000000..f6267f78a9b3c
--- /dev/null
+++ b/docs-theme/sphinx_airflow_theme/version-selector.html
@@ -0,0 +1,31 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+