+ {% for panel in toolbar.panels %}
+ {% include "debug_toolbar/includes/panel_button.html" %}
+ {% endfor %}
+
+
{% for panel in toolbar.panels %}
- {% include "debug_toolbar/includes/panel_button.html" %}
+ {% include "debug_toolbar/includes/panel_content.html" %}
{% endfor %}
-
-
-
-
- DJDT
+
-
-
- {% for panel in toolbar.panels %}
- {% include "debug_toolbar/includes/panel_content.html" %}
- {% endfor %}
-
+
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 2ff363888..a7be8934d 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -439,7 +439,7 @@ could add a **debug_toolbar/base.html** template override to your project:
{% block css %}{{ block.super }}
diff --git a/docs/panels.rst b/docs/panels.rst
index 65024e3c8..ac4ee681f 100644
--- a/docs/panels.rst
+++ b/docs/panels.rst
@@ -444,14 +444,14 @@ Events
.. code-block:: javascript
- import { $$ } from "./utils.js";
+ import { $$, getDebugElement } from "./utils.js";
function addCustomMetrics() {
// Logic to process/add custom metrics here.
// Be sure to cover the case of this function being called twice
// due to file being loaded asynchronously.
}
- const djDebug = document.getElementById("djDebug");
+ const djDebug = getDebugElement();
$$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics);
// Since a panel's scripts are loaded asynchronously, it's possible that
// the above statement would occur after the djdt.panel.render event has
From dd8351a07e0efb9ebad44670e1c3de5731e296b4 Mon Sep 17 00:00:00 2001
From: Tim Schilling
Date: Tue, 2 Dec 2025 21:15:07 -0600
Subject: [PATCH 2/3] Change shadow root element ID for fetching debug element
---
debug_toolbar/static/debug_toolbar/js/utils.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js
index 6a32db892..92254f6e3 100644
--- a/debug_toolbar/static/debug_toolbar/js/utils.js
+++ b/debug_toolbar/static/debug_toolbar/js/utils.js
@@ -76,7 +76,9 @@ function getDebugElement() {
// everywhere the element is being selected. A fixed reference
// to the element should be avoided because the entire DOM could
// be reloaded such as via HTMX boosting.
- const root = document.getElementById("djDebugRoot").shadowRoot;
+ const root = document.getElementById(
+ "djDebugShadowRootContainer"
+ ).shadowRoot;
return root.querySelector("#djDebug");
}
From 23a51b780160ffe8a9600f8636a6d026a021e4ee Mon Sep 17 00:00:00 2001
From: Federico Bond
Date: Wed, 10 Dec 2025 17:52:52 +1100
Subject: [PATCH 3/3] Add USE_SHADOW_DOM setting to opt-in to shadow DOM
toolbar rendering
---
debug_toolbar/settings.py | 1 +
debug_toolbar/static/debug_toolbar/css/toolbar.css | 4 ++--
debug_toolbar/static/debug_toolbar/js/utils.js | 7 ++++---
debug_toolbar/templates/debug_toolbar/base.html | 6 +++---
debug_toolbar/toolbar.py | 5 ++++-
docs/changes.rst | 2 ++
6 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
index ba64c8273..8ba65234b 100644
--- a/debug_toolbar/settings.py
+++ b/debug_toolbar/settings.py
@@ -30,6 +30,7 @@ def _is_running_tests():
"ROOT_TAG_EXTRA_ATTRS": "",
"SHOW_COLLAPSED": False,
"SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar",
+ "USE_SHADOW_DOM": False,
"TOOLBAR_LANGUAGE": None,
"TOOLBAR_STORE_CLASS": "debug_toolbar.store.MemoryStore",
"UPDATE_ON_FETCH": False,
diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css
index 13b88f5c2..13e591492 100644
--- a/debug_toolbar/static/debug_toolbar/css/toolbar.css
+++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css
@@ -1,5 +1,5 @@
/* Variable definitions */
-:host {
+#djDebug {
/* Font families are the same as in Django admin/css/base.css */
--djdt-font-family-primary:
"Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif,
@@ -13,7 +13,7 @@
"Noto Color Emoji";
}
-:host,
+#djDebug,
#djDebug[data-theme="light"] {
--djdt-font-color: black;
--djdt-background-color: white;
diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js
index 92254f6e3..0940c8295 100644
--- a/debug_toolbar/static/debug_toolbar/js/utils.js
+++ b/debug_toolbar/static/debug_toolbar/js/utils.js
@@ -76,9 +76,10 @@ function getDebugElement() {
// everywhere the element is being selected. A fixed reference
// to the element should be avoided because the entire DOM could
// be reloaded such as via HTMX boosting.
- const root = document.getElementById(
- "djDebugShadowRootContainer"
- ).shadowRoot;
+ let root = document.getElementById("djDebugRoot");
+ if (root.shadowRoot) {
+ root = root.shadowRoot;
+ }
return root.querySelector("#djDebug");
}
diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html
index 2c38ed68b..1cd392287 100644
--- a/debug_toolbar/templates/debug_toolbar/base.html
+++ b/debug_toolbar/templates/debug_toolbar/base.html
@@ -1,6 +1,6 @@
{% load i18n static %}
-
diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py
index 0e22c8f06..ce096b78a 100644
--- a/debug_toolbar/toolbar.py
+++ b/debug_toolbar/toolbar.py
@@ -97,7 +97,10 @@ def render_toolbar(self) -> str:
if not self.should_render_panels():
self.init_store()
try:
- context = {"toolbar": self}
+ context = {
+ "toolbar": self,
+ "use_shadow_dom": self.config["USE_SHADOW_DOM"],
+ }
lang = self.config["TOOLBAR_LANGUAGE"] or get_language()
with lang_override(lang):
return render_to_string("debug_toolbar/base.html", context)
diff --git a/docs/changes.rst b/docs/changes.rst
index 948b90dec..7bab177da 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -18,6 +18,8 @@ Pending
* Added test to confirm Django's ``TestCase.assertNumQueries`` works.
* Fixed string representation of values in settings panel.
* Declared support for Django 6.0.
+* Added opt-in support for rendering the toolbar in a shadow DOM for better
+ isolation from the rest of the page.
6.1.0 (2025-10-30)
------------------