diff --git a/python_docs_theme/__init__.py b/python_docs_theme/__init__.py
index bbe1352f..68bb9e31 100644
--- a/python_docs_theme/__init__.py
+++ b/python_docs_theme/__init__.py
@@ -52,12 +52,11 @@ def _html_page_context(
 
 def setup(app):
     current_dir = os.path.abspath(os.path.dirname(__file__))
-    app.add_html_theme(
-        'python_docs_theme', current_dir)
+    app.add_html_theme("python_docs_theme", current_dir)
 
     app.connect("html-page-context", _html_page_context)
 
     return {
-        'parallel_read_safe': True,
-        'parallel_write_safe': True,
+        "parallel_read_safe": True,
+        "parallel_write_safe": True,
     }
diff --git a/python_docs_theme/static/copybutton.js b/python_docs_theme/static/copybutton.js
index 16dee005..7367c4af 100644
--- a/python_docs_theme/static/copybutton.js
+++ b/python_docs_theme/static/copybutton.js
@@ -1,64 +1,92 @@
-$(document).ready(function() {
-    /* Add a [>>>] button on the top-right corner of code samples to hide
+// ``function*`` denotes a generator in JavaScript, see
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
+function* getHideableCopyButtonElements(rootElement) {
+    // yield all elements with the "go" (Generic.Output),
+    // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class
+    for (const el of rootElement.querySelectorAll('.go, .gp, .gt')) {
+        yield el
+    }
+    // tracebacks (.gt) contain bare text elements that need to be
+    // wrapped in a span to hide or show the element
+    for (let el of rootElement.querySelectorAll('.gt')) {
+        while ((el = el.nextSibling) && el.nodeType !== Node.DOCUMENT_NODE) {
+            // stop wrapping text nodes when we hit the next output or
+            // prompt element
+            if (el.nodeType === Node.ELEMENT_NODE && el.matches(".gp, .go")) {
+                break
+            }
+            // if the node is a text node with content, wrap it in a
+            // span element so that we can control visibility
+            if (el.nodeType === Node.TEXT_NODE && el.textContent.trim()) {
+                const wrapper = document.createElement("span")
+                el.after(wrapper)
+                wrapper.appendChild(el)
+                el = wrapper
+            }
+            yield el
+        }
+    }
+}
+
+
+const loadCopyButton = () => {
+    /* Add a [>>>] button in the top-right corner of code samples to hide
      * the >>> and ... prompts and the output and thus make the code
      * copyable. */
-    var div = $('.highlight-python .highlight,' +
-                '.highlight-python3 .highlight,' +
-                '.highlight-pycon .highlight,' +
-                '.highlight-pycon3 .highlight,' +
-                '.highlight-default .highlight');
-    var pre = div.find('pre');
+    const hide_text = "Hide the prompts and output"
+    const show_text = "Show the prompts and output"
 
-    // get the styles from the current theme
-    pre.parent().parent().css('position', 'relative');
-    var hide_text = 'Hide the prompts and output';
-    var show_text = 'Show the prompts and output';
-    var border_width = pre.css('border-top-width');
-    var border_style = pre.css('border-top-style');
-    var border_color = pre.css('border-top-color');
-    var button_styles = {
-        'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
-        'border-color': border_color, 'border-style': border_style,
-        'border-width': border_width, 'color': border_color, 'text-size': '75%',
-        'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',
-        'border-radius': '0 3px 0 0'
+    const button = document.createElement("span")
+    button.classList.add("copybutton")
+    button.innerText = ">>>"
+    button.title = hide_text
+    button.dataset.hidden = "false"
+    const buttonClick = event => {
+        // define the behavior of the button when it's clicked
+        event.preventDefault()
+        const buttonEl = event.currentTarget
+        const codeEl = buttonEl.nextElementSibling
+        if (buttonEl.dataset.hidden === "false") {
+            // hide the code output
+            for (const el of getHideableCopyButtonElements(codeEl)) {
+                el.hidden = true
+            }
+            buttonEl.title = show_text
+            buttonEl.dataset.hidden = "true"
+        } else {
+            // show the code output
+            for (const el of getHideableCopyButtonElements(codeEl)) {
+                el.hidden = false
+            }
+            buttonEl.title = hide_text
+            buttonEl.dataset.hidden = "false"
+        }
     }
 
+    const highlightedElements = document.querySelectorAll(
+        ".highlight-python .highlight,"
+        + ".highlight-python3 .highlight,"
+        + ".highlight-pycon .highlight,"
+        + ".highlight-pycon3 .highlight,"
+        + ".highlight-default .highlight"
+    )
+
     // create and add the button to all the code blocks that contain >>>
-    div.each(function(index) {
-        var jthis = $(this);
-        if (jthis.find('.gp').length > 0) {
-            var button = $('>>>');
-            button.css(button_styles)
-            button.attr('title', hide_text);
-            button.data('hidden', 'false');
-            jthis.prepend(button);
-        }
-        // tracebacks (.gt) contain bare text elements that need to be
-        // wrapped in a span to work with .nextUntil() (see later)
-        jthis.find('pre:has(.gt)').contents().filter(function() {
-            return ((this.nodeType == 3) && (this.data.trim().length > 0));
-        }).wrap('');
-    });
+    highlightedElements.forEach(el => {
+        el.style.position = "relative"
 
-    // define the behavior of the button when it's clicked
-    $('.copybutton').click(function(e){
-        e.preventDefault();
-        var button = $(this);
-        if (button.data('hidden') === 'false') {
-            // hide the code output
-            button.parent().find('.go, .gp, .gt').hide();
-            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden');
-            button.css('text-decoration', 'line-through');
-            button.attr('title', show_text);
-            button.data('hidden', 'true');
-        } else {
-            // show the code output
-            button.parent().find('.go, .gp, .gt').show();
-            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible');
-            button.css('text-decoration', 'none');
-            button.attr('title', hide_text);
-            button.data('hidden', 'false');
+        // if we find a console prompt (.gp), prepend the (deeply cloned) button
+        const clonedButton = button.cloneNode(true)
+        // the onclick attribute is not cloned, set it on the new element
+        clonedButton.onclick = buttonClick
+        if (el.querySelector(".gp") !== null) {
+            el.prepend(clonedButton)
         }
-    });
-});
+    })
+}
+
+if (document.readyState !== "loading") {
+    loadCopyButton()
+} else {
+    document.addEventListener("DOMContentLoaded", loadCopyButton)
+}
diff --git a/python_docs_theme/static/menu.js b/python_docs_theme/static/menu.js
index b2eabb3a..e2335857 100644
--- a/python_docs_theme/static/menu.js
+++ b/python_docs_theme/static/menu.js
@@ -1,55 +1,57 @@
-document.addEventListener('DOMContentLoaded', function () {
+document.addEventListener("DOMContentLoaded", function () {
 
     // Make tables responsive by wrapping them in a div and making them scrollable
-    const tables = document.querySelectorAll('table.docutils');
+    const tables = document.querySelectorAll("table.docutils")
     tables.forEach(function(table){
-        table.outerHTML = '' + table.outerHTML + '
'
-    });
+        table.outerHTML = '' + table.outerHTML + "
"
+    })
 
-    const togglerInput = document.querySelector('.toggler__input');
-    const togglerLabel = document.querySelector('.toggler__label');
-    const sideMenu = document.querySelector('.menu-wrapper');
-    const menuItems = document.querySelectorAll('.menu')
-    const doc = document.querySelector('.document');
-    const body = document.querySelector('body');
+    const togglerInput = document.querySelector(".toggler__input")
+    const togglerLabel = document.querySelector(".toggler__label")
+    const sideMenu = document.querySelector(".menu-wrapper")
+    const menuItems = document.querySelectorAll(".menu")
+    const doc = document.querySelector(".document")
+    const body = document.querySelector("body")
 
     function closeMenu() {
-        togglerInput.checked = false;
-        sideMenu.setAttribute("aria-expanded", 'false');
-        sideMenu.setAttribute('aria-hidden', 'true');
-        togglerLabel.setAttribute('aria-pressed', 'false');
-        body.style.overflow = 'visible';
+        togglerInput.checked = false
+        sideMenu.setAttribute("aria-expanded", "false")
+        sideMenu.setAttribute("aria-hidden", "true")
+        togglerLabel.setAttribute("aria-pressed", "false")
+        body.style.overflow = "visible"
     }
     function openMenu() {
-        togglerInput.checked = true;
-        sideMenu.setAttribute("aria-expanded", 'true');
-        sideMenu.setAttribute('aria-hidden', 'false');
-        togglerLabel.setAttribute('aria-pressed', 'true');
-        body.style.overflow = 'hidden';
+        togglerInput.checked = true
+        sideMenu.setAttribute("aria-expanded", "true")
+        sideMenu.setAttribute("aria-hidden", "false")
+        togglerLabel.setAttribute("aria-pressed", "true")
+        body.style.overflow = "hidden"
     }
 
     // Close menu when link on the sideMenu is clicked
-    sideMenu.addEventListener('click', function (event) {
-        let target = event.target;
-        if (target.tagName.toLowerCase() !== 'a') return;
-        closeMenu();
+    sideMenu.addEventListener("click", function (event) {
+        let target = event.target
+        if (target.tagName.toLowerCase() !== "a") {
+            return
+        }
+        closeMenu()
     })
     // Add accessibility data when sideMenu is opened/closed
-    togglerInput.addEventListener('change', function (e) {
-        togglerInput.checked ? openMenu() : closeMenu();
-    });
+    togglerInput.addEventListener("change", function (_event) {
+        togglerInput.checked ? openMenu() : closeMenu()
+    })
     // Make sideMenu links tabbable only when visible
     for(let menuItem of menuItems) {
         if(togglerInput.checked) {
-          menuItem.setAttribute('tabindex', '0');
+          menuItem.setAttribute("tabindex", "0")
         } else {
-          menuItem.setAttribute('tabindex', '-1');
+          menuItem.setAttribute("tabindex", "-1")
         }
     }
     // Close sideMenu when document body is clicked
-    doc.addEventListener('click', function () {
+    doc.addEventListener("click", function () {
         if (togglerInput.checked) {
-            closeMenu();
+            closeMenu()
         }
     })
 })
\ No newline at end of file
diff --git a/python_docs_theme/static/pydoctheme.css b/python_docs_theme/static/pydoctheme.css
index 6c141f89..5c0d3944 100644
--- a/python_docs_theme/static/pydoctheme.css
+++ b/python_docs_theme/static/pydoctheme.css
@@ -104,10 +104,12 @@ div.sphinxsidebar h4 {
 }
 
 div.sphinxsidebarwrapper {
+    width: 217px;
     box-sizing: border-box;
     height: 100%;
     overflow-x: hidden;
     overflow-y: auto;
+    float: left;
 }
 
 div.sphinxsidebarwrapper > h3:first-child {
@@ -135,6 +137,33 @@ div.sphinxsidebar input[type='text'] {
     max-width: 150px;
 }
 
+#sidebarbutton {
+    /* Sphinx 4.x and earlier compat */
+    height: 100%;
+    background-color: #CCCCCC;
+    margin-left: 0;
+    color: #444444;
+    font-size: 1.2em;
+    cursor: pointer;
+    padding-top: 1px;
+    float: right;
+    display: table;
+    /* after Sphinx 4.x and earlier is dropped, only the below is needed */
+    width: 12px;
+    border-radius: 0 5px 5px 0;
+    border-left: none;
+}
+
+#sidebarbutton span {
+    /* Sphinx 4.x and earlier compat */
+    display: table-cell;
+    vertical-align: middle;
+}
+
+#sidebarbutton:hover {
+    background-color: #AAAAAA;
+}
+
 div.body {
     padding: 0 0 0 1.2em;
 }
@@ -279,6 +308,26 @@ div.genindex-jumpbox a {
     text-align: center;
 }
 
+.copybutton {
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    right: 0;
+    text-size: 75%;
+    font-family: monospace;
+    padding-left: 0.2em;
+    padding-right: 0.2em;
+    border-radius: 0 3px 0 0;
+    color: #ac9; /* follows div.body pre */
+    border-color: #ac9; /* follows div.body pre */
+    border-style: solid; /* follows div.body pre */
+    border-width: 1px; /* follows div.body pre */
+}
+
+.copybutton[data-hidden='true'] {
+    text-decoration: line-through;
+}
+
 @media (max-width: 1023px) {
     /* Body layout */
     div.body {
diff --git a/python_docs_theme/static/sidebar.js b/python_docs_theme/static/sidebar.js
index 6b5c694f..70886d48 100644
--- a/python_docs_theme/static/sidebar.js
+++ b/python_docs_theme/static/sidebar.js
@@ -2,142 +2,83 @@
  * sidebar.js
  * ~~~~~~~~~~
  *
- * This script makes the Sphinx sidebar collapsible. This is a slightly
- * modified version of Sphinx's own sidebar.js.
+ * This file is functionally identical to "sidebar.js" in Sphinx 5.0.
+ * When support for Sphinx 4 and earlier is dropped from the theme,
+ * this file can be removed.
  *
- * .sphinxsidebar contains .sphinxsidebarwrapper.  This script adds in
- * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to
- * collapse and expand the sidebar.
+ * This script makes the Sphinx sidebar collapsible.
  *
- * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the
- * width of the sidebar and the margin-left of the document are decreased.
- * When the sidebar is expanded the opposite happens.  This script saves a
- * per-browser/per-session cookie used to remember the position of the sidebar
- * among the pages.  Once the browser is closed the cookie is deleted and the
- * position reset to the default (expanded).
+ * .sphinxsidebar contains .sphinxsidebarwrapper.  This script adds
+ * in .sphinxsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
+ * used to collapse and expand the sidebar.
  *
- * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
+ * and the width of the sidebar and the margin-left of the document
+ * are decreased. When the sidebar is expanded the opposite happens.
+ * This script saves a per-browser/per-session cookie used to
+ * remember the position of the sidebar among the pages.
+ * Once the browser is closed the cookie is deleted and the position
+ * reset to the default (expanded).
+ *
+ * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
  * :license: BSD, see LICENSE for details.
  *
  */
 
-$(function() {
+const initialiseSidebar = () => {
   // global elements used by the functions.
-  // the 'sidebarbutton' element is defined as global after its
-  // creation, in the add_sidebar_button function
-  var bodywrapper = $('.bodywrapper');
-  var sidebar = $('.sphinxsidebar');
-  var sidebarwrapper = $('.sphinxsidebarwrapper');
-
-  // original margin-left of the bodywrapper and width of the sidebar
-  // with the sidebar expanded
-  var bw_margin_expanded = bodywrapper.css('margin-left');
-  var ssb_width_expanded = sidebar.width();
-
-  // margin-left of the bodywrapper and width of the sidebar
-  // with the sidebar collapsed
-  var bw_margin_collapsed = '.8em';
-  var ssb_width_collapsed = '.8em';
-
-  // colors used by the current theme
-  var dark_color = '#AAAAAA';
-  var light_color = '#CCCCCC';
+  const bodyWrapper = document.getElementsByClassName("bodywrapper")[0]
+  const sidebar = document.getElementsByClassName("sphinxsidebar")[0]
+  const sidebarWrapper = document.getElementsByClassName("sphinxsidebarwrapper")[0]
 
-  function sidebar_is_collapsed() {
-    return sidebarwrapper.is(':not(:visible)');
+  // exit early if the document has no sidebar for some reason
+  if (typeof sidebar === "undefined") {
+    return
   }
 
-  function toggle_sidebar() {
-    if (sidebar_is_collapsed())
-      expand_sidebar();
-    else
-      collapse_sidebar();
-  }
+  // create the sidebar button element
+  const sidebarButton = document.createElement("div")
+  sidebarButton.id = "sidebarbutton"
+  // create the sidebar button arrow element
+  const sidebarArrow = document.createElement("span")
+  sidebarArrow.innerText =  "«"
+  sidebarButton.appendChild(sidebarArrow)
+  sidebar.appendChild(sidebarButton)
 
-  function collapse_sidebar() {
-    sidebarwrapper.hide();
-    sidebar.css('width', ssb_width_collapsed);
-    bodywrapper.css('margin-left', bw_margin_collapsed);
-    sidebarbutton.css({
-        'margin-left': '0',
-        'border-radius': '5px'
-    });
-    sidebarbutton.find('span').text('»');
-    sidebarbutton.attr('title', _('Expand sidebar'));
-    document.cookie = 'sidebar=collapsed';
+  const collapse_sidebar = () => {
+    bodyWrapper.style.marginLeft = ".8em"
+    sidebar.style.width = ".8em"
+    sidebarWrapper.style.display = "none"
+    sidebarArrow.innerText = "»"
+    sidebarButton.title = _("Expand sidebar")
+    window.localStorage.setItem("sidebar", "collapsed")
   }
 
-  function expand_sidebar() {
-    bodywrapper.css('margin-left', bw_margin_expanded);
-    sidebar.css('width', ssb_width_expanded);
-    sidebarwrapper.show();
-    sidebarbutton.css({
-        'margin-left': ssb_width_expanded-12,
-        'border-radius': '0 5px 5px 0'
-    });
-    sidebarbutton.find('span').text('«');
-    sidebarbutton.attr('title', _('Collapse sidebar'));
-    document.cookie = 'sidebar=expanded';
+  const expand_sidebar = () => {
+    bodyWrapper.style.marginLeft = ""
+    sidebar.style.removeProperty("width")
+    sidebarWrapper.style.display = ""
+    sidebarArrow.innerText = "«"
+    sidebarButton.title = _("Collapse sidebar")
+    window.localStorage.setItem("sidebar", "expanded")
   }
 
-  function add_sidebar_button() {
-    sidebarwrapper.css({
-        'float': 'left',
-        'margin-right': '0',
-        'width': ssb_width_expanded - 13
-    });
-    // create the button
-    sidebar.append(
-      ''
-    );
-    var sidebarbutton = $('#sidebarbutton');
-    sidebarbutton.find('span').css({
-        'display': 'block',
-        'position': 'fixed',
-        'top': '50%'
-    });
-
-    sidebarbutton.click(toggle_sidebar);
-    sidebarbutton.attr('title', _('Collapse sidebar'));
-    sidebarbutton.css({
-        'border-radius': '0 5px 5px 0',
-        'color': '#444444',
-        'background-color': '#CCCCCC',
-        'font-size': '1.2em',
-        'cursor': 'pointer',
-        'height': '100%',
-        'padding-left': '1px',
-        'margin-left': ssb_width_expanded - 12
-    });
+  sidebarButton.addEventListener("click", () => {
+    (sidebarWrapper.style.display === "none") ? expand_sidebar() : collapse_sidebar()
+  })
 
-    sidebarbutton.hover(
-      function () {
-          $(this).css('background-color', dark_color);
-      },
-      function () {
-          $(this).css('background-color', light_color);
-      }
-    );
+  const sidebar_state = window.localStorage.getItem("sidebar")
+  if (sidebar_state === "collapsed") {
+    collapse_sidebar()
   }
-
-  function set_position_from_cookie() {
-    if (!document.cookie)
-      return;
-    var items = document.cookie.split(';');
-    for(var k=0; k