diff --git a/.eslintrc.json b/.eslintrc.json
index 5436d43f4d..b4262cf6ed 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -197,6 +197,15 @@
         "unicorn/no-array-for-each": "off"
       }
     },
+    {
+      "files": [
+        "site/assets/js/**"
+      ],
+      "parserOptions": {
+        "sourceType": "module",
+        "ecmaVersion": 2020
+      }
+    },
     {
       "files": [
         "**/*.md"
diff --git a/hugo.yml b/hugo.yml
index a6de700c0c..ba23d38c02 100644
--- a/hugo.yml
+++ b/hugo.yml
@@ -8,8 +8,6 @@ security:
     getenv:
       - ^HUGO_
       - NETLIFY
-  gotemplates:
-    allowActionJSTmpl: true
 
 markup:
   goldmark:
@@ -21,6 +19,9 @@ markup:
     startLevel:         2
     endLevel:           6
 
+build:
+  noJSConfigInAssets:   true
+
 buildDrafts:            true
 buildFuture:            true
 
@@ -44,6 +45,8 @@ module:
       target:           layouts
     - source:           site/static
       target:           static
+    - source:           node_modules/@docsearch/css
+      target:           assets/scss/@docsearch/css
     - source:           site/static/docs/5.3/assets/img/favicons/apple-touch-icon.png
       target:           static/apple-touch-icon.png
     - source:           site/static/docs/5.3/assets/img/favicons/favicon.ico
diff --git a/package-lock.json b/package-lock.json
index 56ffe1e60e..b7d368c30c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,14 +22,17 @@
         "@babel/cli": "^7.23.9",
         "@babel/core": "^7.24.0",
         "@babel/preset-env": "^7.24.0",
+        "@docsearch/js": "^3.6.0",
         "@popperjs/core": "^2.11.8",
         "@rollup/plugin-babel": "^6.0.4",
         "@rollup/plugin-commonjs": "^25.0.7",
         "@rollup/plugin-node-resolve": "^15.2.3",
         "@rollup/plugin-replace": "^5.0.5",
+        "@stackblitz/sdk": "^1.9.0",
         "autoprefixer": "^10.4.18",
         "bundlewatch": "^0.3.3",
         "clean-css-cli": "^5.6.3",
+        "clipboard": "^2.0.11",
         "cross-env": "^7.0.3",
         "eslint": "^8.57.0",
         "eslint-config-xo": "^0.44.0",
@@ -88,6 +91,180 @@
       "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==",
       "dev": true
     },
+    "node_modules/@algolia/autocomplete-core": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz",
+      "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/autocomplete-plugin-algolia-insights": "1.9.3",
+        "@algolia/autocomplete-shared": "1.9.3"
+      }
+    },
+    "node_modules/@algolia/autocomplete-plugin-algolia-insights": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz",
+      "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/autocomplete-shared": "1.9.3"
+      },
+      "peerDependencies": {
+        "search-insights": ">= 1 < 3"
+      }
+    },
+    "node_modules/@algolia/autocomplete-preset-algolia": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz",
+      "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/autocomplete-shared": "1.9.3"
+      },
+      "peerDependencies": {
+        "@algolia/client-search": ">= 4.9.1 < 6",
+        "algoliasearch": ">= 4.9.1 < 6"
+      }
+    },
+    "node_modules/@algolia/autocomplete-shared": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz",
+      "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==",
+      "dev": true,
+      "peerDependencies": {
+        "@algolia/client-search": ">= 4.9.1 < 6",
+        "algoliasearch": ">= 4.9.1 < 6"
+      }
+    },
+    "node_modules/@algolia/cache-browser-local-storage": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz",
+      "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/cache-common": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/cache-common": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz",
+      "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==",
+      "dev": true
+    },
+    "node_modules/@algolia/cache-in-memory": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz",
+      "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/cache-common": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/client-account": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz",
+      "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.22.1",
+        "@algolia/client-search": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/client-analytics": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz",
+      "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.22.1",
+        "@algolia/client-search": "4.22.1",
+        "@algolia/requester-common": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/client-common": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz",
+      "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/client-personalization": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz",
+      "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.22.1",
+        "@algolia/requester-common": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/client-search": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz",
+      "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.22.1",
+        "@algolia/requester-common": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/logger-common": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz",
+      "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==",
+      "dev": true
+    },
+    "node_modules/@algolia/logger-console": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz",
+      "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/logger-common": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/requester-browser-xhr": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz",
+      "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/requester-common": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz",
+      "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==",
+      "dev": true
+    },
+    "node_modules/@algolia/requester-node-http": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz",
+      "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.22.1"
+      }
+    },
+    "node_modules/@algolia/transporter": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz",
+      "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/cache-common": "4.22.1",
+        "@algolia/logger-common": "4.22.1",
+        "@algolia/requester-common": "4.22.1"
+      }
+    },
     "node_modules/@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -1879,6 +2056,54 @@
         "postcss-selector-parser": "^6.0.13"
       }
     },
+    "node_modules/@docsearch/css": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz",
+      "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==",
+      "dev": true
+    },
+    "node_modules/@docsearch/js": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz",
+      "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==",
+      "dev": true,
+      "dependencies": {
+        "@docsearch/react": "3.6.0",
+        "preact": "^10.0.0"
+      }
+    },
+    "node_modules/@docsearch/react": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz",
+      "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/autocomplete-core": "1.9.3",
+        "@algolia/autocomplete-preset-algolia": "1.9.3",
+        "@docsearch/css": "3.6.0",
+        "algoliasearch": "^4.19.1"
+      },
+      "peerDependencies": {
+        "@types/react": ">= 16.8.0 < 19.0.0",
+        "react": ">= 16.8.0 < 19.0.0",
+        "react-dom": ">= 16.8.0 < 19.0.0",
+        "search-insights": ">= 1 < 3"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "search-insights": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -2596,6 +2821,12 @@
       "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
       "dev": true
     },
+    "node_modules/@stackblitz/sdk": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@stackblitz/sdk/-/sdk-1.9.0.tgz",
+      "integrity": "sha512-3m6C7f8pnR5KXys/Hqx2x6ylnpqOak6HtnZI6T5keEO0yT+E4Spkw37VEbdwuC+2oxmjdgq6YZEgiKX7hM1GmQ==",
+      "dev": true
+    },
     "node_modules/@stylistic/stylelint-config": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-1.0.1.tgz",
@@ -3013,6 +3244,28 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/algoliasearch": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz",
+      "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/cache-browser-local-storage": "4.22.1",
+        "@algolia/cache-common": "4.22.1",
+        "@algolia/cache-in-memory": "4.22.1",
+        "@algolia/client-account": "4.22.1",
+        "@algolia/client-analytics": "4.22.1",
+        "@algolia/client-common": "4.22.1",
+        "@algolia/client-personalization": "4.22.1",
+        "@algolia/client-search": "4.22.1",
+        "@algolia/logger-common": "4.22.1",
+        "@algolia/logger-console": "4.22.1",
+        "@algolia/requester-browser-xhr": "4.22.1",
+        "@algolia/requester-common": "4.22.1",
+        "@algolia/requester-node-http": "4.22.1",
+        "@algolia/transporter": "4.22.1"
+      }
+    },
     "node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -4028,6 +4281,17 @@
         "node": ">=4"
       }
     },
+    "node_modules/clipboard": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+      "dev": true,
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
     "node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -4421,6 +4685,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
+      "dev": true
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -6119,6 +6389,15 @@
       "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
       "dev": true
     },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+      "dev": true,
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
     "node_modules/gopd": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -9147,6 +9426,16 @@
       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
       "dev": true
     },
+    "node_modules/preact": {
+      "version": "10.19.6",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz",
+      "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==",
+      "dev": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/preact"
+      }
+    },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -9905,6 +10194,13 @@
         "node": ">=18"
       }
     },
+    "node_modules/search-insights": {
+      "version": "2.13.0",
+      "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz",
+      "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==",
+      "dev": true,
+      "peer": true
+    },
     "node_modules/seek-bzip": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
@@ -9924,6 +10220,12 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
+      "dev": true
+    },
     "node_modules/semver": {
       "version": "6.3.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -11076,6 +11378,12 @@
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
       "dev": true
     },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+      "dev": true
+    },
     "node_modules/tmp": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
diff --git a/package.json b/package.json
index 10491932e1..a8ca542df0 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
     "docs-serve": "hugo server --port 9001 --disableFastRender --noHTTPCache --renderToMemory --printPathWarnings --printUnusedTemplates",
     "docs-serve-only": "npx sirv-cli _site --port 9001",
     "lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json",
-    "update-deps": "ncu -u -x jasmine,karma-browserstack-launcher,karma-rollup-preprocessor && echo Manually update site/assets/js/vendor",
+    "update-deps": "ncu -u -x jasmine,karma-browserstack-launcher,karma-rollup-preprocessor",
     "release": "npm-run-all dist release-sri docs-build release-zip*",
     "release-sri": "node build/generate-sri.mjs",
     "release-version": "node build/change-version.mjs",
@@ -106,14 +106,17 @@
     "@babel/cli": "^7.23.9",
     "@babel/core": "^7.24.0",
     "@babel/preset-env": "^7.24.0",
+    "@docsearch/js": "^3.6.0",
     "@popperjs/core": "^2.11.8",
     "@rollup/plugin-babel": "^6.0.4",
     "@rollup/plugin-commonjs": "^25.0.7",
     "@rollup/plugin-node-resolve": "^15.2.3",
     "@rollup/plugin-replace": "^5.0.5",
+    "@stackblitz/sdk": "^1.9.0",
     "autoprefixer": "^10.4.18",
     "bundlewatch": "^0.3.3",
     "clean-css-cli": "^5.6.3",
+    "clipboard": "^2.0.11",
     "cross-env": "^7.0.3",
     "eslint": "^8.57.0",
     "eslint-config-xo": "^0.44.0",
diff --git a/site/assets/js/application.js b/site/assets/js/application.js
index a16defbd44..ef706ed4d1 100644
--- a/site/assets/js/application.js
+++ b/site/assets/js/application.js
@@ -9,22 +9,12 @@
  * For details, see https://creativecommons.org/licenses/by/3.0/.
  */
 
-(() => {
-  'use strict'
+/* eslint-disable import/no-unresolved */
+import sidebarScroll from 'js/partials/sidebar.js'
+import codeExamples from 'js/partials/code-examples.js'
+import snippets from 'js/partials/snippets.js'
+/* eslint-enable import/no-unresolved */
 
-  // Scroll the active sidebar link into view
-  const sidenav = document.querySelector('.bd-sidebar')
-  const sidenavActiveLink = document.querySelector('.bd-links-nav .active')
-
-  if (sidenav && sidenavActiveLink) {
-    const sidenavHeight = sidenav.clientHeight
-    const sidenavActiveLinkTop = sidenavActiveLink.offsetTop
-    const sidenavActiveLinkHeight = sidenavActiveLink.clientHeight
-    const viewportTop = sidenavActiveLinkTop
-    const viewportBottom = viewportTop - sidenavHeight + sidenavActiveLinkHeight
-
-    if (sidenav.scrollTop > viewportTop || sidenav.scrollTop < viewportBottom) {
-      sidenav.scrollTop = viewportTop - (sidenavHeight / 2) + (sidenavActiveLinkHeight / 2)
-    }
-  }
-})()
+sidebarScroll()
+codeExamples()
+snippets()
diff --git a/site/assets/js/code-examples.js b/site/assets/js/partials/code-examples.js
similarity index 97%
rename from site/assets/js/code-examples.js
rename to site/assets/js/partials/code-examples.js
index 3462da59ba..d81191a965 100644
--- a/site/assets/js/code-examples.js
+++ b/site/assets/js/partials/code-examples.js
@@ -2,18 +2,18 @@
 // IT'S ALL JUST JUNK FOR OUR DOCS!
 // ++++++++++++++++++++++++++++++++++++++++++
 
-/*!
+/*
  * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
  * Copyright 2011-2024 The Bootstrap Authors
  * Licensed under the Creative Commons Attribution 3.0 Unported License.
  * For details, see https://creativecommons.org/licenses/by/3.0/.
  */
 
-/* global ClipboardJS: false, bootstrap: false */
+/* global bootstrap: false */
 
-(() => {
-  'use strict'
+import ClipboardJS from 'clipboard'
 
+export default () => {
   // Insert copy to clipboard button before .highlight
   const btnTitle = 'Copy to clipboard'
   const btnEdit = 'Edit on StackBlitz'
@@ -87,4 +87,4 @@
       tooltipBtn.setContent({ '.tooltip-inner': btnTitle })
     }, { once: true })
   })
-})()
+}
diff --git a/site/assets/js/partials/sidebar.js b/site/assets/js/partials/sidebar.js
new file mode 100644
index 0000000000..c43f414819
--- /dev/null
+++ b/site/assets/js/partials/sidebar.js
@@ -0,0 +1,30 @@
+// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
+// IT'S ALL JUST JUNK FOR OUR DOCS!
+// ++++++++++++++++++++++++++++++++++++++++++
+
+/*
+ * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors
+ * Licensed under the Creative Commons Attribution 3.0 Unported License.
+ * For details, see https://creativecommons.org/licenses/by/3.0/.
+ */
+
+export default () => {
+  // Scroll the active sidebar link into view
+  const sidenav = document.querySelector('.bd-sidebar')
+  const sidenavActiveLink = document.querySelector('.bd-links-nav .active')
+
+  if (!sidenav || !sidenavActiveLink) {
+    return
+  }
+
+  const sidenavHeight = sidenav.clientHeight
+  const sidenavActiveLinkTop = sidenavActiveLink.offsetTop
+  const sidenavActiveLinkHeight = sidenavActiveLink.clientHeight
+  const viewportTop = sidenavActiveLinkTop
+  const viewportBottom = viewportTop - sidenavHeight + sidenavActiveLinkHeight
+
+  if (sidenav.scrollTop > viewportTop || sidenav.scrollTop < viewportBottom) {
+    sidenav.scrollTop = viewportTop - (sidenavHeight / 2) + (sidenavActiveLinkHeight / 2)
+  }
+}
diff --git a/site/assets/js/partials/snippets.js b/site/assets/js/partials/snippets.js
new file mode 100644
index 0000000000..1771612cf0
--- /dev/null
+++ b/site/assets/js/partials/snippets.js
@@ -0,0 +1,168 @@
+// NOTICE!!! Initially embedded in our docs this JavaScript
+// file contains elements that can help you create reproducible
+// use cases in StackBlitz for instance.
+// In a real project please adapt this content to your needs.
+// ++++++++++++++++++++++++++++++++++++++++++
+
+/*
+ * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors
+ * Licensed under the Creative Commons Attribution 3.0 Unported License.
+ * For details, see https://creativecommons.org/licenses/by/3.0/.
+ */
+
+/* global bootstrap: false */
+
+export default () => {
+  // --------
+  // Tooltips
+  // --------
+  // Instantiate all tooltips in a docs or StackBlitz
+  document.querySelectorAll('[data-bs-toggle="tooltip"]')
+    .forEach(tooltip => {
+      new bootstrap.Tooltip(tooltip)
+    })
+
+  // --------
+  // Popovers
+  // --------
+  // Instantiate all popovers in docs or StackBlitz
+  document.querySelectorAll('[data-bs-toggle="popover"]')
+    .forEach(popover => {
+      new bootstrap.Popover(popover)
+    })
+
+  // -------------------------------
+  // Toasts
+  // -------------------------------
+  // Used by 'Placement' example in docs or StackBlitz
+  const toastPlacement = document.getElementById('toastPlacement')
+  if (toastPlacement) {
+    document.getElementById('selectToastPlacement').addEventListener('change', function () {
+      if (!toastPlacement.dataset.originalClass) {
+        toastPlacement.dataset.originalClass = toastPlacement.className
+      }
+
+      toastPlacement.className = `${toastPlacement.dataset.originalClass} ${this.value}`
+    })
+  }
+
+  // Instantiate all toasts in docs pages only
+  document.querySelectorAll('.bd-example .toast')
+    .forEach(toastNode => {
+      const toast = new bootstrap.Toast(toastNode, {
+        autohide: false
+      })
+
+      toast.show()
+    })
+
+  // Instantiate all toasts in docs pages only
+  // js-docs-start live-toast
+  const toastTrigger = document.getElementById('liveToastBtn')
+  const toastLiveExample = document.getElementById('liveToast')
+
+  if (toastTrigger) {
+    const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
+    toastTrigger.addEventListener('click', () => {
+      toastBootstrap.show()
+    })
+  }
+  // js-docs-end live-toast
+
+  // -------------------------------
+  // Alerts
+  // -------------------------------
+  // Used in 'Show live alert' example in docs or StackBlitz
+
+  // js-docs-start live-alert
+  const alertPlaceholder = document.getElementById('liveAlertPlaceholder')
+  const appendAlert = (message, type) => {
+    const wrapper = document.createElement('div')
+    wrapper.innerHTML = [
+      `<div class="alert alert-${type} alert-dismissible" role="alert">`,
+      `   <div>${message}</div>`,
+      '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
+      '</div>'
+    ].join('')
+
+    alertPlaceholder.append(wrapper)
+  }
+
+  const alertTrigger = document.getElementById('liveAlertBtn')
+  if (alertTrigger) {
+    alertTrigger.addEventListener('click', () => {
+      appendAlert('Nice, you triggered this alert message!', 'success')
+    })
+  }
+  // js-docs-end live-alert
+
+  // --------
+  // Carousels
+  // --------
+  // Instantiate all non-autoplaying carousels in docs or StackBlitz
+  document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')
+    .forEach(carousel => {
+      bootstrap.Carousel.getOrCreateInstance(carousel)
+    })
+
+  // -------------------------------
+  // Checks & Radios
+  // -------------------------------
+  // Indeterminate checkbox example in docs and StackBlitz
+  document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
+    .forEach(checkbox => {
+      if (checkbox.id.includes('Indeterminate')) {
+        checkbox.indeterminate = true
+      }
+    })
+
+  // -------------------------------
+  // Links
+  // -------------------------------
+  // Disable empty links in docs examples only
+  document.querySelectorAll('.bd-content [href="#"]')
+    .forEach(link => {
+      link.addEventListener('click', event => {
+        event.preventDefault()
+      })
+    })
+
+  // -------------------------------
+  // Modal
+  // -------------------------------
+  // Modal 'Varying modal content' example in docs and StackBlitz
+  // js-docs-start varying-modal-content
+  const exampleModal = document.getElementById('exampleModal')
+  if (exampleModal) {
+    exampleModal.addEventListener('show.bs.modal', event => {
+      // Button that triggered the modal
+      const button = event.relatedTarget
+      // Extract info from data-bs-* attributes
+      const recipient = button.getAttribute('data-bs-whatever')
+      // If necessary, you could initiate an Ajax request here
+      // and then do the updating in a callback.
+
+      // Update the modal's content.
+      const modalTitle = exampleModal.querySelector('.modal-title')
+      const modalBodyInput = exampleModal.querySelector('.modal-body input')
+
+      modalTitle.textContent = `New message to ${recipient}`
+      modalBodyInput.value = recipient
+    })
+  }
+  // js-docs-end varying-modal-content
+
+  // -------------------------------
+  // Offcanvas
+  // -------------------------------
+  // 'Offcanvas components' example in docs only
+  const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')
+  if (myOffcanvas) {
+    myOffcanvas.forEach(offcanvas => {
+      offcanvas.addEventListener('show.bs.offcanvas', event => {
+        event.preventDefault()
+      }, false)
+    })
+  }
+}
diff --git a/site/assets/js/search.js b/site/assets/js/search.js
index b095d3bbd9..95d920c445 100644
--- a/site/assets/js/search.js
+++ b/site/assets/js/search.js
@@ -2,18 +2,25 @@
 // IT'S ALL JUST JUNK FOR OUR DOCS!
 // ++++++++++++++++++++++++++++++++++++++++++
 
-(() => {
-  'use strict'
+/*!
+ * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
+ * Copyright 2024 The Bootstrap Authors
+ * Licensed under the Creative Commons Attribution 3.0 Unported License.
+ * For details, see https://creativecommons.org/licenses/by/3.0/.
+ */
 
+import docsearch from '@docsearch/js'
+
+(() => {
   const searchElement = document.getElementById('docsearch')
 
-  if (!window.docsearch || !searchElement) {
+  if (!searchElement) {
     return
   }
 
   const siteDocsVersion = searchElement.getAttribute('data-bd-docs-version')
 
-  window.docsearch({
+  docsearch({
     apiKey: '3151f502c7b9e9dafd5e6372b691a24e',
     indexName: 'bootstrap',
     appId: 'AK7KMZKZHQ',
diff --git a/site/assets/js/snippets.js b/site/assets/js/snippets.js
index 94d8f6deee..3fce5e437f 100644
--- a/site/assets/js/snippets.js
+++ b/site/assets/js/snippets.js
@@ -1,170 +1,15 @@
-// NOTICE!!! Initially embedded in our docs this JavaScript
-// file contains elements that can help you create reproducible
-// use cases in StackBlitz for instance.
-// In a real project please adapt this content to your needs.
-// ++++++++++++++++++++++++++++++++++++++++++
-
-/*!
+/*
  * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
  * Copyright 2011-2024 The Bootstrap Authors
  * Licensed under the Creative Commons Attribution 3.0 Unported License.
  * For details, see https://creativecommons.org/licenses/by/3.0/.
  */
 
-/* global bootstrap: false */
+// Note that this file is not published; we only include it in scrpts.html
+// for StackBlitz to work
 
-(() => {
-  'use strict'
+/* eslint-disable import/no-unresolved */
+import snippets from 'js/partials/snippets.js'
+/* eslint-enable import/no-unresolved */
 
-  // --------
-  // Tooltips
-  // --------
-  // Instantiate all tooltips in a docs or StackBlitz
-  document.querySelectorAll('[data-bs-toggle="tooltip"]')
-    .forEach(tooltip => {
-      new bootstrap.Tooltip(tooltip)
-    })
-
-  // --------
-  // Popovers
-  // --------
-  // Instantiate all popovers in docs or StackBlitz
-  document.querySelectorAll('[data-bs-toggle="popover"]')
-    .forEach(popover => {
-      new bootstrap.Popover(popover)
-    })
-
-  // -------------------------------
-  // Toasts
-  // -------------------------------
-  // Used by 'Placement' example in docs or StackBlitz
-  const toastPlacement = document.getElementById('toastPlacement')
-  if (toastPlacement) {
-    document.getElementById('selectToastPlacement').addEventListener('change', function () {
-      if (!toastPlacement.dataset.originalClass) {
-        toastPlacement.dataset.originalClass = toastPlacement.className
-      }
-
-      toastPlacement.className = `${toastPlacement.dataset.originalClass} ${this.value}`
-    })
-  }
-
-  // Instantiate all toasts in docs pages only
-  document.querySelectorAll('.bd-example .toast')
-    .forEach(toastNode => {
-      const toast = new bootstrap.Toast(toastNode, {
-        autohide: false
-      })
-
-      toast.show()
-    })
-
-  // Instantiate all toasts in docs pages only
-  // js-docs-start live-toast
-  const toastTrigger = document.getElementById('liveToastBtn')
-  const toastLiveExample = document.getElementById('liveToast')
-
-  if (toastTrigger) {
-    const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
-    toastTrigger.addEventListener('click', () => {
-      toastBootstrap.show()
-    })
-  }
-  // js-docs-end live-toast
-
-  // -------------------------------
-  // Alerts
-  // -------------------------------
-  // Used in 'Show live alert' example in docs or StackBlitz
-
-  // js-docs-start live-alert
-  const alertPlaceholder = document.getElementById('liveAlertPlaceholder')
-  const appendAlert = (message, type) => {
-    const wrapper = document.createElement('div')
-    wrapper.innerHTML = [
-      `<div class="alert alert-${type} alert-dismissible" role="alert">`,
-      `   <div>${message}</div>`,
-      '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
-      '</div>'
-    ].join('')
-
-    alertPlaceholder.append(wrapper)
-  }
-
-  const alertTrigger = document.getElementById('liveAlertBtn')
-  if (alertTrigger) {
-    alertTrigger.addEventListener('click', () => {
-      appendAlert('Nice, you triggered this alert message!', 'success')
-    })
-  }
-  // js-docs-end live-alert
-
-  // --------
-  // Carousels
-  // --------
-  // Instantiate all non-autoplaying carousels in docs or StackBlitz
-  document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')
-    .forEach(carousel => {
-      bootstrap.Carousel.getOrCreateInstance(carousel)
-    })
-
-  // -------------------------------
-  // Checks & Radios
-  // -------------------------------
-  // Indeterminate checkbox example in docs and StackBlitz
-  document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
-    .forEach(checkbox => {
-      if (checkbox.id.includes('Indeterminate')) {
-        checkbox.indeterminate = true
-      }
-    })
-
-  // -------------------------------
-  // Links
-  // -------------------------------
-  // Disable empty links in docs examples only
-  document.querySelectorAll('.bd-content [href="#"]')
-    .forEach(link => {
-      link.addEventListener('click', event => {
-        event.preventDefault()
-      })
-    })
-
-  // -------------------------------
-  // Modal
-  // -------------------------------
-  // Modal 'Varying modal content' example in docs and StackBlitz
-  // js-docs-start varying-modal-content
-  const exampleModal = document.getElementById('exampleModal')
-  if (exampleModal) {
-    exampleModal.addEventListener('show.bs.modal', event => {
-      // Button that triggered the modal
-      const button = event.relatedTarget
-      // Extract info from data-bs-* attributes
-      const recipient = button.getAttribute('data-bs-whatever')
-      // If necessary, you could initiate an Ajax request here
-      // and then do the updating in a callback.
-
-      // Update the modal's content.
-      const modalTitle = exampleModal.querySelector('.modal-title')
-      const modalBodyInput = exampleModal.querySelector('.modal-body input')
-
-      modalTitle.textContent = `New message to ${recipient}`
-      modalBodyInput.value = recipient
-    })
-  }
-  // js-docs-end varying-modal-content
-
-  // -------------------------------
-  // Offcanvas
-  // -------------------------------
-  // 'Offcanvas components' example in docs only
-  const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')
-  if (myOffcanvas) {
-    myOffcanvas.forEach(offcanvas => {
-      offcanvas.addEventListener('show.bs.offcanvas', event => {
-        event.preventDefault()
-      }, false)
-    })
-  }
-})()
+snippets()
diff --git a/site/assets/js/stackblitz.js b/site/assets/js/stackblitz.js
new file mode 100644
index 0000000000..c6f44b0f4f
--- /dev/null
+++ b/site/assets/js/stackblitz.js
@@ -0,0 +1,67 @@
+// NOTICE!!! Initially embedded in our docs this JavaScript
+// file contains elements that can help you create reproducible
+// use cases in StackBlitz for instance.
+// In a real project please adapt this content to your needs.
+// ++++++++++++++++++++++++++++++++++++++++++
+
+/*!
+ * JavaScript for Bootstrap's docs (https://getbootstrap.com/)
+ * Copyright 2024 The Bootstrap Authors
+ * Licensed under the Creative Commons Attribution 3.0 Unported License.
+ * For details, see https://creativecommons.org/licenses/by/3.0/.
+ */
+
+import sdk from '@stackblitz/sdk'
+// https://gohugo.io/hugo-pipes/js/#options
+import {
+  cssCdn, docsVersion, jsBundleCdn, jsSnippetFile
+} from '@params' // eslint-disable-line import/no-unresolved
+
+// Open in StackBlitz logic
+document.querySelectorAll('.btn-edit').forEach(btn => {
+  btn.addEventListener('click', event => {
+    const codeSnippet = event.target.closest('.bd-code-snippet')
+    const exampleEl = codeSnippet.querySelector('.bd-example')
+
+    const htmlSnippet = exampleEl.innerHTML
+    const jsSnippet = codeSnippet.querySelector('.btn-edit').getAttribute('data-sb-js-snippet')
+    // Get extra classes for this example
+    const classes = Array.from(exampleEl.classList).join(' ')
+
+    sdk.openBootstrapSnippet(htmlSnippet, jsSnippet, classes)
+  })
+})
+
+sdk.openBootstrapSnippet = (htmlSnippet, jsSnippet, classes) => {
+  const markup = `<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link href="${cssCdn}" rel="stylesheet">
+    <link href="https://getbootstrap.com/docs/${docsVersion}/assets/css/docs.css" rel="stylesheet">
+    <title>Bootstrap Example</title>
+    <${'script'} src="${jsBundleCdn}"></${'script'}>
+  </head>
+  <body class="p-3 m-0 border-0 ${classes}">
+
+    <!-- Example Code -->
+${htmlSnippet.replace(/^/gm, '    ')}
+    <!-- End Example Code -->
+  </body>
+</html>`
+
+  const jsSnippetContent = jsSnippet ? jsSnippetFile : null
+  const project = {
+    files: {
+      'index.html': markup,
+      'index.js': jsSnippetContent
+    },
+    title: 'Bootstrap Example',
+    description: `Official example from ${window.location.href}`,
+    template: jsSnippet ? 'javascript' : 'html',
+    tags: ['bootstrap']
+  }
+
+  sdk.openProject(project, { openFile: 'index.html' })
+}
diff --git a/site/assets/js/vendor/clipboard.min.js b/site/assets/js/vendor/clipboard.min.js
deleted file mode 100644
index 1103f811ed..0000000000
--- a/site/assets/js/vendor/clipboard.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * clipboard.js v2.0.11
- * https://clipboardjs.com/
- *
- * Licensed MIT © Zeno Rocha
- */
-!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body},n="";return"string"==typeof t?n=o(t,e):t instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(null==t?void 0:t.type)?n=o(t.value,e):(n=r()(t),c("copy")),n};function l(t){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var s=function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},e=t.action,n=void 0===e?"copy":e,o=t.container,e=t.target,t=t.text;if("copy"!==n&&"cut"!==n)throw new Error('Invalid "action" value, use either "copy" or "cut"');if(void 0!==e){if(!e||"object"!==l(e)||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===n&&e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===n&&(e.hasAttribute("readonly")||e.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes')}return t?f(t,{container:o}):e?"cut"===n?a(e):f(e,{container:o}):void 0};function p(t){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function d(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function y(t,e){return(y=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function h(n){var o=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}();return function(){var t,e=v(n);return t=o?(t=v(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments),e=this,!(t=t)||"object"!==p(t)&&"function"!=typeof t?function(t){if(void 0!==t)return t;throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}(e):t}}function v(t){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function m(t,e){t="data-clipboard-".concat(t);if(e.hasAttribute(t))return e.getAttribute(t)}var b=function(){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&y(t,e)}(r,i());var t,e,n,o=h(r);function r(t,e){var n;return function(t){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")}(this),(n=o.call(this)).resolveOptions(e),n.listenClick(t),n}return t=r,n=[{key:"copy",value:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body};return f(t,e)}},{key:"cut",value:function(t){return a(t)}},{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof t?[t]:t,e=!!document.queryCommandSupported;return t.forEach(function(t){e=e&&!!document.queryCommandSupported(t)}),e}}],(e=[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===p(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=u()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget,n=this.action(e)||"copy",t=s({action:n,container:this.container,target:this.target(e),text:this.text(e)});this.emit(t?"success":"error",{action:n,text:t,trigger:e,clearSelection:function(){e&&e.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(t){return m("action",t)}},{key:"defaultTarget",value:function(t){t=m("target",t);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(t){return m("text",t)}},{key:"destroy",value:function(){this.listener.destroy()}}])&&d(t.prototype,e),n&&d(t,n),r}()},828:function(t){var e;"undefined"==typeof Element||Element.prototype.matches||((e=Element.prototype).matches=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector),t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},438:function(t,e,n){var u=n(828);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=u(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},879:function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},370:function(t,e,n){var f=n(879),l=n(438);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!f.string(e))throw new TypeError("Second argument must be a String");if(!f.fn(n))throw new TypeError("Third argument must be a Function");if(f.node(t))return c=e,a=n,(u=t).addEventListener(c,a),{destroy:function(){u.removeEventListener(c,a)}};if(f.nodeList(t))return o=t,r=e,i=n,Array.prototype.forEach.call(o,function(t){t.addEventListener(r,i)}),{destroy:function(){Array.prototype.forEach.call(o,function(t){t.removeEventListener(r,i)})}};if(f.string(t))return t=t,e=e,n=n,l(document.body,t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,u,c,a}},817:function(t){t.exports=function(t){var e,n="SELECT"===t.nodeName?(t.focus(),t.value):"INPUT"===t.nodeName||"TEXTAREA"===t.nodeName?((e=t.hasAttribute("readonly"))||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),e||t.removeAttribute("readonly"),t.value):(t.hasAttribute("contenteditable")&&t.focus(),n=window.getSelection(),(e=document.createRange()).selectNodeContents(t),n.removeAllRanges(),n.addRange(e),n.toString());return n}},279:function(t){function e(){}e.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,u=o.length;i<u;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=e,t.exports.TinyEmitter=e}},r={},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,{a:e}),e},o.d=function(t,e){for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o(686).default;function o(t){if(r[t])return r[t].exports;var e=r[t]={exports:{}};return n[t](e,e.exports,o),e.exports}var n,r});
\ No newline at end of file
diff --git a/site/assets/scss/_search.scss b/site/assets/scss/_search.scss
index 592d65f98f..4bd9d80724 100644
--- a/site/assets/scss/_search.scss
+++ b/site/assets/scss/_search.scss
@@ -1,5 +1,7 @@
 // stylelint-disable selector-class-pattern
 
+@import "@docsearch/css/dist/style";
+
 :root {
   --docsearch-primary-color: var(--bd-violet);
   --docsearch-logo-color: var(--bd-violet);
diff --git a/site/content/docs/5.3/components/alerts.md b/site/content/docs/5.3/components/alerts.md
index 6103898bcd..deea8f2e9c 100644
--- a/site/content/docs/5.3/components/alerts.md
+++ b/site/content/docs/5.3/components/alerts.md
@@ -38,7 +38,7 @@ Click the button below to show an alert (hidden with inline styles to start), th
 
 We use the following JavaScript to trigger our live alert demo:
 
-{{< js-docs name="live-alert" file="site/assets/js/snippets.js" >}}
+{{< js-docs name="live-alert" file="site/assets/js/partials/snippets.js" >}}
 
 ### Link color
 
diff --git a/site/content/docs/5.3/components/modal.md b/site/content/docs/5.3/components/modal.md
index 3ca7cc4d22..e00931e816 100644
--- a/site/content/docs/5.3/components/modal.md
+++ b/site/content/docs/5.3/components/modal.md
@@ -481,7 +481,7 @@ Below is a live demo followed by example HTML and JavaScript. For more informati
 </div>
 {{< /example >}}
 
-{{< js-docs name="varying-modal-content" file="site/assets/js/snippets.js" >}}
+{{< js-docs name="varying-modal-content" file="site/assets/js/partials/snippets.js" >}}
 
 ### Toggle between modals
 
diff --git a/site/content/docs/5.3/components/toasts.md b/site/content/docs/5.3/components/toasts.md
index a7d1cb7130..4beae7dc89 100644
--- a/site/content/docs/5.3/components/toasts.md
+++ b/site/content/docs/5.3/components/toasts.md
@@ -87,7 +87,7 @@ Click the button below to show a toast (positioned with our utilities in the low
 
 We use the following JavaScript to trigger our live toast demo:
 
-{{< js-docs name="live-toast" file="site/assets/js/snippets.js" >}}
+{{< js-docs name="live-toast" file="site/assets/js/partials/snippets.js" >}}
 
 ### Translucent
 
diff --git a/site/content/docs/5.3/docsref.md b/site/content/docs/5.3/docsref.md
index d0fc42f3db..6b811f47d1 100644
--- a/site/content/docs/5.3/docsref.md
+++ b/site/content/docs/5.3/docsref.md
@@ -46,4 +46,4 @@ sitemap_exclude: true
 
 {{< scss-docs name="variable-gradient" file="scss/_variables.scss" >}}
 
-{{< js-docs name="live-toast" file="site/assets/js/snippets.js" >}}
+{{< js-docs name="live-toast" file="site/assets/js/partials/snippets.js" >}}
diff --git a/site/layouts/partials/header.html b/site/layouts/partials/header.html
index ba58893286..3f259ac5e8 100644
--- a/site/layouts/partials/header.html
+++ b/site/layouts/partials/header.html
@@ -7,11 +7,13 @@
 <meta name="docsearch:language" content="en">
 <meta name="docsearch:version" content="{{ .Site.Params.docs_version }}">
 
-<title>{{ if .IsHome }}{{ .Site.Title | markdownify }} · {{ .Site.Params.subtitle | markdownify }}{{ else }}{{ .Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}{{ end }}</title>
-
 <link rel="canonical" href="{{ .Permalink }}">
 
-<link rel="preconnect" href="https://AK7KMZKZHQ-dsn.algolia.net" crossorigin>
+{{ if (ne .Page.Layout "examples") -}}
+<link rel="preconnect" href="https://ak7kmzkzhq-dsn.algolia.net" crossorigin>
+{{- end }}
+
+<title>{{ if .IsHome }}{{ .Site.Title | markdownify }} · {{ .Site.Params.subtitle | markdownify }}{{ else }}{{ .Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}{{ end }}</title>
 
 {{ with .Params.robots -}}
 <meta name="robots" content="{{ . }}">
diff --git a/site/layouts/partials/scripts.html b/site/layouts/partials/scripts.html
index 046f659a94..00c5c97827 100644
--- a/site/layouts/partials/scripts.html
+++ b/site/layouts/partials/scripts.html
@@ -4,70 +4,31 @@
   <script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js"></script>
 {{- end }}
 
-<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@3"></script>
-
-{{ if eq .Page.Layout "docs" -}}
-<script src="https://cdn.jsdelivr.net/npm/@stackblitz/sdk@1/bundles/sdk.umd.js"></script>
-{{- end }}
-
-{{- $vendor := resources.Match "js/vendor/*.js" -}}
-{{- $js := resources.Match "js/*.js" -}}
-{{- $targetDocsJSPath := path.Join "/docs" .Site.Params.docs_version "assets/js/docs.js" -}}
-{{- $docsJs := append $js $vendor | resources.Concat $targetDocsJSPath -}}
+{{- $esbuildOptions := dict "target" "es2019" -}}
+{{- $targetDocsJSPath := path.Join "/docs" .Site.Params.docs_version -}}
 
 {{- if hugo.IsProduction -}}
-  {{- $docsJs = $docsJs | resources.Minify -}}
+  {{- $esbuildOptions = merge $esbuildOptions (dict "minify" "true") -}}
 {{- end }}
 
-<script src="{{ $docsJs.Permalink | relURL }}"></script>
+{{- $applicationJs := resources.Get "js/application.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/application.js") }}
+<script src="{{ $applicationJs.RelPermalink }}"></script>
+
+{{- if (ne .Page.Layout "examples") -}}
+{{- $searchJs := resources.Get "js/search.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/search.js") }}
+<script async src="{{ $searchJs.RelPermalink }}"></script>
+{{- end -}}
 
 {{ if eq .Page.Layout "docs" -}}
-<script>
-  // Open in StackBlitz logic
-  document.querySelectorAll('.btn-edit').forEach(btn => {
-    btn.addEventListener('click', event => {
-      const htmlSnippet = event.target.closest('.bd-code-snippet').querySelector('.bd-example').innerHTML
-
-      // Get extra classes for this example
-      const classes = Array.from(event.target.closest('.bd-code-snippet').querySelector('.bd-example').classList).join(' ')
-
-      const jsSnippet = event.target.closest('.bd-code-snippet').querySelector('.btn-edit').getAttribute('data-sb-js-snippet')
-      StackBlitzSDK.openBootstrapSnippet(htmlSnippet, jsSnippet, classes)
-    })
-  })
-
-  StackBlitzSDK.openBootstrapSnippet = (htmlSnippet, jsSnippet, classes) => {
-    const markup = `<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link href="{{ .Site.Params.cdn.css }}" rel="stylesheet">
-    <link href="https://getbootstrap.com/docs/{{ .Site.Params.docs_version }}/assets/css/docs.css" rel="stylesheet">
-    <title>Bootstrap Example</title>
-    <${'script'} src="{{ .Site.Params.cdn.js_bundle }}"></${'script'}>
-  </head>
-  <body class="p-3 m-0 border-0 ${classes}">
-
-    <!-- Example Code -->
-${htmlSnippet.replace(/^/gm, '    ')}
-    <!-- End Example Code -->
-  </body>
-</html>`
-
-    const jsSnippetContent = jsSnippet ? '{{ os.ReadFile "site/assets/js/snippets.js" }}' : null
-    const project = {
-      files: {
-        'index.html': markup,
-        'index.js': jsSnippetContent
-      },
-      title: 'Bootstrap Example',
-      description: `Official example from ${window.location.href}`,
-      template: jsSnippet ? 'javascript' : 'html',
-      tags: ['bootstrap']
-    }
-
-    StackBlitzSDK.openProject(project, { openFile: 'index.html' })
-  }
-</script>
-{{- end }}
+{{- /* Disable minify options for snippets.js so that the file's readable on StackBlitz */ -}}
+{{- $snippetsFile := resources.Get "js/snippets.js" | js.Build (merge $esbuildOptions (dict "minify" "false")) -}}
+{{- $esbuildParams := dict
+  "cssCdn" .Site.Params.cdn.css
+  "jsBundleCdn" .Site.Params.cdn.js_bundle
+  "docsVersion" .Site.Params.docs_version
+  "jsSnippetFile" $snippetsFile.Content
+-}}
+{{- $esbuildOptions = merge $esbuildOptions (dict "params" $esbuildParams) -}}
+{{- $stackblitzJs := resources.Get "js/stackblitz.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/stackblitz.js") }}
+<script async src="{{ $stackblitzJs.RelPermalink }}"></script>
+{{- end -}}
diff --git a/site/layouts/partials/stylesheet.html b/site/layouts/partials/stylesheet.html
index f675d72125..69e75a2711 100644
--- a/site/layouts/partials/stylesheet.html
+++ b/site/layouts/partials/stylesheet.html
@@ -1,5 +1,3 @@
-<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
-
 {{ if hugo.IsProduction -}}
 {{ if eq .Page.Params.direction "rtl" -}}
 <link href="/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap.rtl.min.css" rel="stylesheet" {{ printf "integrity=%q" .Site.Params.cdn.css_rtl_hash | safeHTMLAttr }}>
@@ -21,5 +19,5 @@
 
 {{- $style := resources.Get "scss/docs.scss" | toCSS $sassOptions | postCSS $postcssOptions }}
 
-<link href="{{ $style.Permalink | relURL }}" rel="stylesheet">
+<link href="{{ $style.RelPermalink }}" rel="stylesheet">
 {{- end }}