diff --git a/themes/fid_adlr/js/check_save_statuses_adlr.js b/themes/fid_adlr/js/check_save_statuses_adlr.js
index 8a638c5a8bab9967cdd75a685a40e6f3fea5ca21..40a75100135811be01fb397cb6b62368112e829c 100644
--- a/themes/fid_adlr/js/check_save_statuses_adlr.js
+++ b/themes/fid_adlr/js/check_save_statuses_adlr.js
@@ -1,3 +1,14 @@
+/**
+ * origin: bootstrap3 - check_save_statuses.js
+ *
+ * usage:
+ * - highlight favorite (saved) records as filled star
+ *
+ * modified for adlr:
+ * - #16706: overrides only one method from check_save_statuses.js
+ * --> add this js-dependency to search/results.phtml too
+ * - #21409: adjustments for subrecords on result groups
+ */
 function displaySaveStatus(itemLists, $item) {
   if (itemLists.length > 0) {
     // If we got lists back, display them!
diff --git a/themes/fid_adlr/js/facets.js b/themes/fid_adlr/js/facets.js
index 7898cf42331b125c6c5a85f43ef9f8e372c1b603..02354e50aee0a01a0facc6a9619e69d11d0c56ce 100644
--- a/themes/fid_adlr/js/facets.js
+++ b/themes/fid_adlr/js/facets.js
@@ -1,5 +1,13 @@
+/**
+ * origin: bootstrap3
+ *
+ * usage:
+ * - facet post load, start new filtered searches
+ *
+ * modified for adlr:
+ * - #16514: set spinner instead of loading "..."
+ */
 /*global VuFind */
-// refs 16514: copied from bootstrap3, set spinner instead of loading ..., see below - GG
 /*exported collapseTopFacets, initFacetTree */
 function buildFacetNodes(data, currentPath, allowExclude, excludeTitle, counts)
 {
@@ -27,8 +35,11 @@ function buildFacetNodes(data, currentPath, allowExclude, excludeTitle, counts)
       $i.appendTo($item);
       $item.append(' ');
     }
+    var $description = $('<span/>')
+      .addClass('facet-value')
+      .append(this.displayText);
+    $item.append($description);
 
-    $item.append(this.displayText);
     $item.appendTo($html);
 
     if (!this.isApplied && counts) {
@@ -104,25 +115,23 @@ function initFacetTree(treeNode, inSidebar)
   }
   treeNode.data('loaded', true);
 
-  var source = treeNode.data('source');
-  var facet = treeNode.data('facet');
-  var operator = treeNode.data('operator');
-  var sort = treeNode.data('sort');
-  var query = window.location.href.split('?')[1];
-
   if (inSidebar) {
     treeNode.prepend('<li class="list-group-item"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i></li>');
   } else {
     treeNode.prepend('<div><i class="fa fa-spinner fa-spin" aria-hidden="true"></i><div>');
   }
-  $.getJSON(VuFind.path + '/AJAX/JSON?' + query,
-    {
-      method: "getFacetData",
-      source: source,
-      facetName: facet,
-      facetSort: sort,
-      facetOperator: operator
-    },
+  var request = {
+    method: "getFacetData",
+    source: treeNode.data('source'),
+    facetName: treeNode.data('facet'),
+    facetSort: treeNode.data('sort'),
+    facetOperator: treeNode.data('operator'),
+    query: treeNode.data('query'),
+    querySuppressed: treeNode.data('querySuppressed'),
+    extraFields: treeNode.data('extraFields')
+  };
+  $.getJSON(VuFind.path + '/AJAX/JSON?' + request.query,
+    request,
     function getFacetData(response/*, textStatus*/) {
       buildFacetTree(treeNode, response.data.facets, inSidebar);
     }
@@ -150,7 +159,7 @@ VuFind.register('sideFacets', function SideFacets() {
     // refs 16514: set spinner instead of loading ... - GG
     var overlay = '<div class="facet-loading-overlay">'
       + '<span class="facet-loading-overlay-label"><i class="fa fa-spinner fa-spin fa-3x"></i>'
-      + '</span></div>';
+      + "</span></div>";
     // refs 16514: set spinner instead of loading ... END - GG
     $(this).closest(".collapse").append(overlay);
     // This callback operates both as a click handler and a JSTree callback;
@@ -178,18 +187,18 @@ VuFind.register('sideFacets', function SideFacets() {
     if (facetList.length === 0) {
       return;
     }
-    var urlParts = window.location.href.split('?');
-    var query = urlParts.length > 1 ? urlParts[1] : '';
     var request = {
       method: 'getSideFacets',
       searchClassId: $container.data('searchClassId'),
       location: $container.data('location'),
       configIndex: $container.data('configIndex'),
-      query: query,
+      query: $container.data('query'),
+      querySuppressed: $container.data('querySuppressed'),
+      extraFields: $container.data('extraFields'),
       enabledFacets: facetList
     };
     $container.find('.facet-load-indicator').removeClass('hidden');
-    $.getJSON(VuFind.path + '/AJAX/JSON?' + query, request)
+    $.getJSON(VuFind.path + '/AJAX/JSON?' + request.query, request)
       .done(function onGetSideFacetsDone(response) {
         $.each(response.data.facets, function initFacet(facet, facetData) {
           var containerSelector = typeof facetData.checkboxCount !== 'undefined'
@@ -251,7 +260,7 @@ VuFind.register('sideFacets', function SideFacets() {
           $.support.transition = false;
           if ((' ' + storedItem + ' ').indexOf(' in ') > -1) {
             $(item).collapse('show');
-          } else {
+          } else if (!$(item).data('forceIn')) {
             $(item).collapse('hide');
           }
         } finally {
@@ -269,6 +278,16 @@ VuFind.register('sideFacets', function SideFacets() {
         loadAjaxSideFacets();
       });
     loadAjaxSideFacets();
+
+    // Keep filter dropdowns on screen
+    $(".search-filter-dropdown").on("shown.bs.dropdown", function checkFilterDropdownWidth(e) {
+      var $dropdown = $(e.target).find(".dropdown-menu");
+      if ($(e.target).position().left + $dropdown.width() >= window.innerWidth) {
+        $dropdown.addClass("dropdown-menu-right");
+      } else {
+        $dropdown.removeClass("dropdown-menu-right");
+      }
+    });
   }
 
   return { init: init, showLoadingOverlay: showLoadingOverlay };
diff --git a/themes/fid_adlr/js/lightbox.js b/themes/fid_adlr/js/lightbox.js
index baffb1c183982fe7db303643f8f7424f005d9c99..8478fca4b46968da2876ebb2e8e197d56a66ecfa 100644
--- a/themes/fid_adlr/js/lightbox.js
+++ b/themes/fid_adlr/js/lightbox.js
@@ -1,13 +1,24 @@
-// refs 16504: copied from bootstrap3 to set an overlay, see below - GG
+/**
+ * origin: finc
+ *
+ * usage:
+ * - lightbox
+ *
+ * modified for adlr:
+ * - #16584: set design for closing button of lightbox
+ * - #16833 (#20154): remove save search icon by disabled one if save request succeeded
+ * - 16504: overlay spinner login
+ */
 /*global grecaptcha, recaptchaOnLoad, resetCaptcha, VuFind */
 VuFind.register('lightbox', function Lightbox() {
   // State
   var _originalUrl = false;
   var _currentUrl = false;
-  var _lightboxTitle = '';
+  var _lightboxTitle = false;
   var refreshOnClose = false;
   var _modalParams = {};
   // Elements
+  // finc: keep _origin - see #17984
   var _modal, _modalBody, _clickedButton, _origin = null;
   // Utilities
   function _storeClickedStatus() {
@@ -16,14 +27,11 @@ VuFind.register('lightbox', function Lightbox() {
   function _html(content) {
     _modalBody.html(content);
     // Set or update title if we have one
-    if (_lightboxTitle !== '') {
-      var h2 = _modalBody.find('h2:first-child');
-      if (h2.length === 0) {
-        h2 = $('<h2/>').prependTo(_modalBody);
+    var $h2 = _modalBody.find("h2:first-of-type");
+    if (_lightboxTitle && $h2) {
+      $h2.text(_lightboxTitle);
       }
-      h2.text(_lightboxTitle);
-      _lightboxTitle = '';
-    }
+    _lightboxTitle = false;
     _modal.modal('handleUpdate');
   }
   function _emit(msg, _details) {
@@ -44,17 +52,20 @@ VuFind.register('lightbox', function Lightbox() {
   }
 
   // Public: Present an alert
+  // finc: keep role='alert' - see #18988 and #18790
   function showAlert(message, _type) {
     var type = _type || 'info';
-    _html('<div class="flash-message alert alert-' + type + '">' + message + '</div>'
+    // fid_adlr: #16584 set design for closing button of lightbox
+    _html('<div class="flash-message alert alert-' + type + '" role="alert">' + message + '</div>'
         + '<button class="btn btn-primary" data-dismiss="modal">' + VuFind.translate('close') + '</button>');
     _modal.modal('show');
   }
+  // finc: keep role='alert' - see #18988 and #18790
   function flashMessage(message, _type) {
     var type = _type || 'info';
     _modalBody.find('.flash-message,.fa.fa-spinner').remove();
     _modalBody.find('h2:first-of-type')
-      .after('<div class="flash-message alert alert-' + type + '">' + message + '</div>');
+      .after('<div class="flash-message alert alert-' + type + '" role="alert">' + message + '</div>');
   }
   function close() {
     _modal.modal('hide');
@@ -116,6 +127,16 @@ VuFind.register('lightbox', function Lightbox() {
     $('#modal').find('.checkbox-select-item').change(function lbSelectAllDisable() {
       $(this).closest('.modal-body').find('.checkbox-select-all').prop('checked', false);
     });
+
+    // #19695 accessibility: set id for aria-label
+    if (_modalBody.find('h1').length) {
+      _modalBody.find('h1:first').attr('id', 'modal-title');
+      $('#modal').attr('aria-labelledby', 'modal-title');
+    } else if (_modalBody.find('h2').length) {
+      _modalBody.find('h2:first').attr('id', 'modal-title');
+      $('#modal').attr('aria-labelledby', 'modal-title');
+    }
+
     // Recaptcha
     recaptchaOnLoad();
   }
@@ -145,9 +166,15 @@ VuFind.register('lightbox', function Lightbox() {
     _xhr.always(function lbAjaxAlways() { _xhr = false; })
       .done(function lbAjaxDone(content, status, jq_xhr) {
         var errorMsgs = [];
-        if (jq_xhr.status !== 205) {
+        var flashMessages = [];
+        if (jq_xhr.status === 204) {
+          // No content, close lightbox
+          close();
+          return;
+        } else if (jq_xhr.status !== 205) {
           var testDiv = $('<div/>').html(content);
           errorMsgs = testDiv.find('.flash-message.alert-danger:not([data-lightbox-ignore])');
+          flashMessages = testDiv.find('.flash-message:not([data-lightbox-ignore])');
           // Place Hold error isolation
           if (obj.url.match(/\/Record\/.*(Hold|Request)\?/)) {
             if (errorMsgs.length && testDiv.find('.record').length) {
@@ -168,7 +195,7 @@ VuFind.register('lightbox', function Lightbox() {
           obj.method && (
             obj.url.match(/catalogLogin/)
             || obj.url.match(/MyResearch\/(?!Bulk|Delete|Recover)/)
-          ) && errorMsgs.length === 0
+          ) && flashMessages.length === 0
         ) {
 
           var eventResult = _emit('VuFind.lightbox.login', {
@@ -180,7 +207,8 @@ VuFind.register('lightbox', function Lightbox() {
               VuFind.refreshPage();
             }
             return false;
-          } else {
+          /* FINC-specific: avoid page reload after login if param is given - show account links by javascript */
+          } else if (new URL(_originalUrl).searchParams.get('refreshOnClose') !== 'false' || !displayLogin()) {
             VuFind.lightbox.refreshOnClose = true;
           }
           _currentUrl = _originalUrl; // Now that we're logged in, where were we?
@@ -190,7 +218,7 @@ VuFind.register('lightbox', function Lightbox() {
           return;
         }
 
-        /* remove save search icon by disabled one if save request succeeded */
+        /* adlr_link: #16833 (#20154), remove save search icon by disabled one if save request succeeded */
         if (_originalUrl !== undefined && _originalUrl.match(/SaveSearch\?save=/)) {
           $("#save-search").toggleClass("hidden");
           $("#saved-search").toggleClass("hidden");
@@ -207,9 +235,20 @@ VuFind.register('lightbox', function Lightbox() {
     ajax({ url: _currentUrl || _originalUrl });
   }
 
-  // finc: restore focus after deleting records, #20379 #21905
-  function setOrigin(origin) {
-    _origin = origin;
+  /**
+   * Finc specific: Login without Page Reload
+   */
+  function displayLogin() {
+    try {
+      if ($('#loginOptions').length && $('.logoutOptions').length) {
+        userIsLoggedIn = true;
+        $('#loginOptions').hide();
+        $('.logoutOptions').removeClass('hidden');
+        return true;
+      }
+    } catch (e) { /* Empty */ }
+
+    return false;
   }
 
   /**
@@ -246,25 +285,31 @@ VuFind.register('lightbox', function Lightbox() {
    * data-lightbox-title = Lightbox title (overrides any title the page provides)
    */
   _constrainLink = function constrainLink(event) {
-    if (typeof $(this).data('lightboxIgnore') != 'undefined'
-      || typeof this.attributes.href === 'undefined'
-      || this.attributes.href.value.charAt(0) === '#'
-      || this.href.match(/^[a-zA-Z]+:[^/]/) // ignore resource identifiers (mailto:, tel:, etc.)
+    var $link = $(this);
+    if (typeof $link.data("lightboxIgnore") != "undefined"
+      || typeof $link.attr("href") === "undefined"
+      || $link.attr("href").charAt(0) === "#"
+      || $link.attr("href").match(/^[a-zA-Z]+:[^/]/) // ignore resource identifiers (mailto:, tel:, etc.)
+      || (typeof $link.attr("target") !== "undefined"
+        && (
+          $link.attr("target").toLowerCase() === "_new"
+          || $link.attr("target").toLowerCase() === "new"
+        ))
     ) {
       return true;
     }
     if (this.href.length > 1) {
       event.preventDefault();
-      var obj = {url: $(this).data('lightboxHref') || this.href};
-      if ("string" === typeof $(this).data('lightboxPost')) {
+      var obj = {url: $(this).data('lightbox-href') || this.href};
+      if ("string" === typeof $(this).data('lightbox-post')) {
         obj.type = 'POST';
-        obj.data = $(this).data('lightboxPost');
+        obj.data = $(this).data('lightbox-post');
       }
-      _lightboxTitle = $(this).data('lightboxTitle') || '';
+      _lightboxTitle = $(this).data('lightbox-title') || false;
       _modalParams = $(this).data();
       VuFind.modal('show');
       // Copied finc #17984
-      _origin = $(this);
+      setOrigin($(this));
       ajax(obj);
       _currentUrl = this.href;
       return false;
@@ -288,13 +333,6 @@ VuFind.register('lightbox', function Lightbox() {
     // Gather data
     var form = event.target;
     var data = $(form).serializeArray();
-    // Check for recaptcha
-    if (typeof grecaptcha !== 'undefined') {
-      var recaptcha = $(form).find('.g-recaptcha');
-      if (recaptcha.length > 0) {
-        data.push({ name: 'g-recaptcha-response', value: grecaptcha.getResponse(recaptcha.data('captchaId')) });
-      }
-    }
     // Force layout
     data.push({ name: 'layout', value: 'lightbox' }); // Return in lightbox, please
     // Add submit button information
@@ -342,7 +380,7 @@ VuFind.register('lightbox', function Lightbox() {
       submit.attr('disabled', 'disabled');
     }
     // Store custom title
-    _lightboxTitle = submit.data('lightboxTitle') || $(form).data('lightboxTitle') || '';
+    _lightboxTitle = submit.data('lightbox-title') || $(form).data('lightbox-title') || false;
     // Get Lightbox content
     ajax({
       url: $(form).attr('action') || _currentUrl,
@@ -356,6 +394,99 @@ VuFind.register('lightbox', function Lightbox() {
     return false;
   };
 
+  /**
+   * Keyboard and focus controllers
+   * Adapted from Micromodal
+   * - https://github.com/ghosh/Micromodal/blob/master/lib/src/index.js
+   */
+  var FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
+  function getFocusableNodes () {
+    var nodes = _modal[0].querySelectorAll(FOCUSABLE_ELEMENTS);
+    return [].slice.apply(nodes);
+  }
+  /**
+   * Tries to set focus on a node which is not a close trigger
+   * if no other nodes exist then focuses on first close trigger
+   */
+  function setFocusToFirstNode() {
+    var focusableNodes = getFocusableNodes();
+
+    // no focusable nodes
+    if (focusableNodes.length === 0) return;
+
+    // remove nodes on whose click, the modal closes
+    var nodesWhichAreNotCloseTargets = focusableNodes.filter(function nodeFilter(node) {
+      return !node.hasAttribute("data-lightbox-close") && (
+        !node.hasAttribute("data-dismiss") ||
+        node.getAttribute("data-dismiss") !== "modal"
+      );
+    });
+
+    if (nodesWhichAreNotCloseTargets.length > 0) {
+      nodesWhichAreNotCloseTargets[0].focus();
+    }
+    if (nodesWhichAreNotCloseTargets.length === 0) {
+      focusableNodes[0].focus();
+    }
+  }
+
+  function retainFocus(event) {
+    var focusableNodes = getFocusableNodes();
+
+    // no focusable nodes
+    if (focusableNodes.length === 0) return;
+
+    /**
+     * Filters nodes which are hidden to prevent
+     * focus leak outside modal
+     */
+    focusableNodes = focusableNodes.filter(function nodeHiddenFilter(node) {
+      return (node.offsetParent !== null);
+    });
+
+    // if disableFocus is true
+    if (!_modal[0].contains(document.activeElement)) {
+      focusableNodes[0].focus();
+    } else {
+      var focusedItemIndex = focusableNodes.indexOf(document.activeElement);
+
+      if (event.shiftKey && focusedItemIndex === 0) {
+        focusableNodes[focusableNodes.length - 1].focus();
+        event.preventDefault();
+      }
+
+      if (
+        !event.shiftKey &&
+        focusableNodes.length > 0 &&
+        focusedItemIndex === focusableNodes.length - 1
+      ) {
+        focusableNodes[0].focus();
+        event.preventDefault();
+      }
+    }
+  }
+
+  // finc: restore focus after deleting records, #20379
+  function setOrigin(origin) {
+    _origin = origin;
+  }
+
+  function onKeydown(event) {
+    if (event.keyCode === 27) { // esc
+      close();
+    }
+    if (event.keyCode === 9) { // tab
+      retainFocus(event);
+    }
+  }
+  function bindFocus() {
+    document.addEventListener('keydown', onKeydown);
+    setFocusToFirstNode();
+  }
+  function unbindFocus() {
+    document.removeEventListener('keydown', onKeydown);
+  }
+
   // Public: Attach listeners to the page
   function bind(el) {
     var target = el || document;
@@ -402,7 +533,7 @@ VuFind.register('lightbox', function Lightbox() {
     _html(VuFind.translate('loading') + '...');
     _originalUrl = false;
     _currentUrl = false;
-    _lightboxTitle = '';
+    _lightboxTitle = false;
     _modalParams = {};
   }
   function init() {
@@ -411,23 +542,30 @@ VuFind.register('lightbox', function Lightbox() {
     _modal.on('hide.bs.modal', function lightboxHide() {
       if (VuFind.lightbox.refreshOnClose) {
         VuFind.refreshPage();
-      }
+      } else {
+        unbindFocus();
       this.setAttribute('aria-hidden', true);
       _emit('VuFind.lightbox.closing');
+      }
     });
     _modal.on('hidden.bs.modal', function lightboxHidden() {
       VuFind.lightbox.reset();
       _emit('VuFind.lightbox.closed');
-      // copied from finc #17984
-      // set focus back on launching element
+      // finc: set focus back on launching element
       if (_origin !== null && _origin !== 'undefined') {
         _origin.focus();
       }
     });
+    _modal.on("shown.bs.modal", function lightboxShown() {
+      bindFocus();
+    });
 
     VuFind.modal = function modalShortcut(cmd) {
       if (cmd === 'show') {
         _modal.modal($.extend({ show: true }, _modalParams)).attr('aria-hidden', false);
+
+        // Set keyboard focus
+        setFocusToFirstNode();
       } else {
         _modal.modal(cmd);
       }
@@ -450,8 +588,9 @@ VuFind.register('lightbox', function Lightbox() {
     render: render,
     // Reset
     reset: reset,
+    // finc: restore focus after deleting records, #20379
+    setOrigin: setOrigin,
     // Init
-    init: init,
-    setOrigin: setOrigin
+    init: init
   };
 });
diff --git a/themes/fid_adlr/js/resultGrouping.js b/themes/fid_adlr/js/resultGrouping.js
index 86ec4002d63cdc85d5370cefe2c2ed55f7d10ebe..805bcdd17f2e0deaf37f61464e22d8ebc57c4313 100644
--- a/themes/fid_adlr/js/resultGrouping.js
+++ b/themes/fid_adlr/js/resultGrouping.js
@@ -1,3 +1,14 @@
+/**
+ * origin: vufind-results-grouping - resultGrouping.js
+ *
+ * usage:
+ * - en/disabling result grouping via js (see sidebar)
+ *
+ * modified for adlr:
+ * - #21409:
+ * -- showing spinning wheel while reloading
+ * -- prevent browser prompt on reload page via JS
+ */
 $(document).ready(function groupItems() {
     $('.results-group-toggle').click(function(e){
         $(this).parent().toggleClass('active');
diff --git a/themes/fid_adlr/js/user_delivery_address.js b/themes/fid_adlr/js/user_delivery_address.js
index 3bdcf428e2c93a7abca235f28cfe6ee7bfe57eac..309871cea9ace975921bb7ffc124bc26dfbd2b0c 100644
--- a/themes/fid_adlr/js/user_delivery_address.js
+++ b/themes/fid_adlr/js/user_delivery_address.js
@@ -1,3 +1,12 @@
+/**
+ * origin: -
+ *
+ * usage:
+ * - add (on show) / remove (on hide) required attributes on private address inputs
+ *
+ * modified for adlr:
+ * - implemented with #20900
+ */
 function showHidePrivateAddress()
 {
     var requiredInputs = ["line1", "zip", "city", "country"];