diff --git a/local/config/vufind/FincILS.ini b/local/config/vufind/FincILS.ini
index f288a15ef6a89018c6e24217a42ae9db35309f06..d255b92b531bdf33863c64144edbdbd21e2ce8a3 100644
--- a/local/config/vufind/FincILS.ini
+++ b/local/config/vufind/FincILS.ini
@@ -95,4 +95,11 @@ queryIls[] = 'getFacetAvail:Local'
 ; through user input. E.g. to select an email profile according to the selected
 ; pickUpLocation you have to set the value to "pickUpLocation".
 ; By default it is left empty using the default profile "EmailHold".
-;emailProfileSelector = 
+;emailProfileSelector =
+
+; the ILLRequests section contains regex patterns for inter library loan item detection
+; itemPattern matches the item ID, labelPattern matches the item's label
+; as used in a PAIA regex filter
+;[ILLRequests]
+;itemPattern = "/^(?:(?!DE-15)).*$/"
+;labelPattern = "/^ILL Status String$"
\ No newline at end of file
diff --git a/local/config/vufind/FincLibero.ini b/local/config/vufind/FincLibero.ini
new file mode 100644
index 0000000000000000000000000000000000000000..26fb4a666a515219f64e3131dccad0fea6de5e75
--- /dev/null
+++ b/local/config/vufind/FincLibero.ini
@@ -0,0 +1,52 @@
+; General Settings for all FincLibero instances
+[Parent_Config]
+;inherits FincILS settings from same directory
+relative_path=FincILS.ini
+
+; entries in [RequiredConfig] denote settings that are tested during initialization
+; of FincLibero. Syntax is
+; <INI Section>[<key within section>] = true for required, false for not required
+; e.g. General[departmentLocationBase] = true makes FincLibero::init()
+; throw an InitException in case [General]->departmentLocationBase is not set
+; in any of FincLibero.ini or FincILS.ini
+;[RequiredConfig]
+;General[departmentLocationBase] = true
+;General[titleHoldLimitations] = true
+;General[pickUpLocationPatterns] = true
+;General[requestableLimitations] = true
+;General[holdableLimitations] = true
+;General[recallableLimitations] = true
+;General[awlLimitations] = true
+;General[stackURIs] = true
+;General[readingRoomURIs] = true
+
+; the following section is here to list info and examples for the settings
+; that may be defined as required in [RequiredConfig]
+; examples collected in DE-15 and DE-Zwi2
+;[General]
+; Pattern that will extend uris for department
+;departmentLocationBase = "http://data.ub.uni-leipzig.de/resource/DE-Zwi2/department/zw"
+
+; Limitations that will trigger an action.
+;titleHoldLimitations[] = "DE-Zwi2:OrderTitle:[0-9]+X?"
+
+; Patterns that will identify limitations as pickUpLocations
+;pickUpLocationPatterns[] = "/^http:\/\/data\.ub\.uni-leipzig\.de\/resource\//"
+
+; Limitations that will trigger the placeStorageRetrievalRequest action
+;requestableLimitations[] = DE-15:OrderRequired
+
+; Limitations that will trigger the placeHold action
+;holdableLimitations[] = DE-15:URI
+
+; Limitations that will trigger a recall action (currently not used).
+;recallableLimitations[] = DE-15:ReservationPossible
+
+; Limitations that will identify the item for being bound to another item.
+;awlLimitations[] = DE-15:OrderViaBoundItem
+
+; URIs that will be used for stack views
+;stackURIs[] = "http://data.ub.uni-leipzig.de/resource/DE-15/pickup/zw01thek"
+
+; URIs that will be used for reading room views
+;readingRoomURIs[] = "http://data.ub.uni-leipzig.de/resource/DE-15/pickup/zw01thekfo"
diff --git a/local/languages/de.ini b/local/languages/de.ini
index fc2852d71200a869d5c844346094cca46eb94636..6428ea9568274ecf180264ac9f35778dece09cc1 100644
--- a/local/languages/de.ini
+++ b/local/languages/de.ini
@@ -2054,7 +2054,6 @@ DE-Kn38 = "Hochschule für Musik und Tanz Köln"
 Range-from-to = "Bereich von/bis"
 
 ; #17833
-form-legend = "Bitte füllen Sie alle Felder aus"
 form-button-submit = "Ausgefülltes Formular abschicken"
 
 ; #17601
@@ -2079,4 +2078,6 @@ missing_record_exception = "Der aufgerufene Titel (%%id%%) ist nicht vorhanden."
 Unknown Electronic = "Titel ist beim Resolver-Service nicht bekannt"
 
 ; #20826
-title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%"
\ No newline at end of file
+title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%"
+
+load_tab_content_hint = "Klicken Sie hier, um den Inhalt der Registerkarte zu laden."
diff --git a/local/languages/en.ini b/local/languages/en.ini
index 48a6d2dbab0cae35c423264ffc0f5f85d83fe4fb..f1aa7b7cf478120dce8ec464464eec64899cca2b 100644
--- a/local/languages/en.ini
+++ b/local/languages/en.ini
@@ -975,7 +975,7 @@ DE-15-292 = "Leipzig University Library, Central Library Medicine"
 DE-105 = "Technische Universität Bergakademie Freiberg"
 DE-197 = "City Library Leipzig"
 DE-520 = "Dresden University of Applied Sciences"
-DE-540 = "Dresden Academy of Fine Arts"
+DE-540 = "Dresden University of Fine Arts"
 DE-1156 = "Folkwang University of Arts"
 DE-1972 = "Robert Schumann Academy Düsseldorf"
 DE-Ch1 = "Technische Universität Chemnitz"
@@ -1949,7 +1949,6 @@ resolver_link_access_unknown = "Record unknown to resolver"
 ; message to be shown upon empty resolver response
 no_resolver_links = "No online links available."
 
-
 ; reset password
 reset_password_text = "Please complete the form below to reset your password. You will receive an email after we have completed resetting your password."
 Reset Password = "Reset Password"
@@ -2136,7 +2135,6 @@ DE-Kn38 = "Hochschule für Musik und Tanz Köln"
 Range-from-to = "Range from/to"
 
 ; #17833
-form-legend = "Please fill in all fields to create an account"
 form-button-submit = "Submit the completed form"
 
 ; #17601
@@ -2167,4 +2165,6 @@ missing_record_exception = "Record %%id%% is unavailable."
 Unknown Electronic = "Record is unknown to the resolver service"
 
 ; #20826
-title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%"
\ No newline at end of file
+title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%"
+
+load_tab_content_hint = "Click to load tab content."
diff --git a/module/finc/src/finc/ILS/Connection.php b/module/finc/src/finc/ILS/Connection.php
index 4ddbdddb7def752e7bd4613feefb22c14e280a71..a7f05faf35fdc579c430ccbc362c746858fe0187 100644
--- a/module/finc/src/finc/ILS/Connection.php
+++ b/module/finc/src/finc/ILS/Connection.php
@@ -91,4 +91,87 @@ class Connection extends \VuFind\ILS\Connection implements TranslatorAwareInterf
         }
         return $response;
     }
+
+    /**
+     * Check ILLRequests
+     *
+     * A support method for checkFunction(). This is responsible for checking
+     * the driver configuration to determine if the system supports ILL requests.
+     *
+     * @param array $functionConfig The ILL request configuration values
+     * @param array $params         An array of function-specific params (or null)
+     *
+     * @return mixed On success, an associative array with specific function keys
+     * and values either for placing requests via a form; on failure, false.
+     */
+    protected function checkMethodILLRequests($functionConfig, $params)
+    {
+        if (
+            method_exists($this, 'getMyILLRequests')
+            &&
+            isset($functionConfig['daiaILLpattern'])
+        ) {
+            return ['daiaILLpattern' => $functionConfig['daiaILLpattern']];
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Get access to the driver object.
+     * Extends parent function with a bypass of failover on InitException
+     *
+     * @param bool $init Should we initialize the driver (if necessary), or load it
+     * "as-is"?
+     *
+     * @throws \Exception
+     * @throws InitException
+     * @return object
+     */
+    public function getDriver($init = true)
+    {
+        if (null === $this->driver) {
+            $this->setDriver($this->driverManager->get($this->config->driver));
+        }
+        if (!$this->driverInitialized && $init) {
+            try {
+                $this->initializeDriver();
+            } catch (InitException $e) {
+                throw $e;
+            } catch (\Exception $e) {
+                if (!$this->failOverToNoILS()) {
+                    throw $e;
+                }
+            }
+        }
+        return $this->driver;
+    }
+
+    /**
+     * Get Hidden Login Mode
+     *
+     * This is responsible for indicating whether login should be hidden.
+     *
+     * Extends parent function with a bypass of failover on InitException
+     *
+     * @throws \Exception
+     * @throws InitException
+     * @return bool true if the login should be hidden, false if not
+     */
+    public function loginIsHidden()
+    {
+        // Graceful degradation -- return false if no method supported.
+        try {
+            return $this->checkCapability('loginIsHidden')
+                ? $this->getDriver()->loginIsHidden() : false;
+        } catch (InitException $e) {
+            throw $e;
+        } catch (\Exception $e) {
+            if ($this->failOverToNoILS()) {
+                return call_user_func_array([$this, __METHOD__], func_get_args());
+            }
+            throw $e;
+        }
+    }
 }
diff --git a/module/finc/src/finc/ILS/Driver/FincILS.php b/module/finc/src/finc/ILS/Driver/FincILS.php
index 6a204440bdc41f2122b145e283d04b7763521c23..7f8f971642d890cfab5d37eb0ae675cc449a01a1 100644
--- a/module/finc/src/finc/ILS/Driver/FincILS.php
+++ b/module/finc/src/finc/ILS/Driver/FincILS.php
@@ -108,6 +108,22 @@ class FincILS extends PAIA implements LoggerAwareInterface
      */
     protected $isil;
 
+    /**
+     * Regex to be used in getMyILLRequests,
+     * finds item IDs denoting inter library loan requests
+     *
+     * @var string
+     */
+    protected $illItemPattern;
+
+    /**
+     * Regex to be used in getMyILLRequests,
+     * finds item labels denoting inter library loan requests
+     *
+     * @var string
+     */
+    protected $illLabelPattern;
+
     /**
      * Connection timeout in seconds used for _testILSConnection()
      *
@@ -223,6 +239,16 @@ class FincILS extends PAIA implements LoggerAwareInterface
         $this->ilsTestTimeout = isset($this->config['General'])
             && isset($this->config['General']['ilsTestTimeout'])
                 ? $this->config['General']['ilsTestTimeout'] : 90;
+
+        // set filter for reserved item IDs at ILL request
+        $this->illItemPattern =
+            (isset($this->config['ILLRequests']['itemPattern']))
+                ? $this->config['ILLRequests']['itemPattern'] : null;
+
+        // set filter for reserved item labels at ILL request
+        $this->illLabelPattern =
+            (isset($this->config['ILLRequests']['labelPattern']))
+                ? $this->config['ILLRequests']['labelPattern'] : null;
     }
 
     /**
@@ -1704,4 +1730,34 @@ class FincILS extends PAIA implements LoggerAwareInterface
         // overriden in FincLibero
         return $limitations;
     }
+
+    /**
+     * Customized getMyILLRequests, relies on PAIA-URI pattern
+     * @param array $patron
+     * @return array
+     */
+    public function getMyILLRequests($patron)
+    {
+        // filters for getMyILLRequests are:
+        // document.item = URI has to match config pattern
+
+        if (!empty($this->illItemPattern)) {
+            $filter['regex']['item'] = $this->illItemPattern;
+        }
+
+        if (!empty($this->illLabelPattern)) {
+            // filter out some item according their status label
+            // cf. #15214
+            $filter['regex']['label'] = $this->illLabelPattern;
+        }
+
+        if (!empty($filter)) {
+            // get items-docs for given filters
+            $items = $this->paiaGetItems($patron, $filter);
+
+            return $this->mapPaiaItems($items, 'myHoldsMapping');
+        }
+
+        return [];
+    }
 }
diff --git a/module/finc/src/finc/ILS/Driver/FincLibero.php b/module/finc/src/finc/ILS/Driver/FincLibero.php
index 116bcf3881f4989ecc7f945dc03a3c83dad32a86..91d958f1bf604b53a71c8e72ecc1e3c9763c7219 100644
--- a/module/finc/src/finc/ILS/Driver/FincLibero.php
+++ b/module/finc/src/finc/ILS/Driver/FincLibero.php
@@ -27,6 +27,7 @@
  * @link     http://vufind.org/wiki/vufind2:building_an_ils_driver Wiki
  */
 namespace finc\ILS\Driver;
+use finc\ILS\InitException;
 use VuFind\I18n\Translator\TranslatorAwareTrait;
 use VuFind\I18n\Translator\TranslatorAwareInterface,
     VuFind\Exception\ILS as ILSException;
@@ -122,8 +123,136 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
     public function init()
     {
         parent::init();
-        $this->boundItemIdPattern = $this->config['General']['bound_item_id_pattern'] ?? null;
-        $this->boundItemLabelPattern = $this->config['General']['bound_item_label_pattern'] ?? null;
+
+        $this->setMemberFromConfig('boundItemPattern', 'General', 'bound_item_id_pattern');
+        $this->setMemberFromConfig('boundItemLabelPattern', 'General',
+            'bound_item_label_pattern');
+
+        // Get the base URI to extend departments.
+        $this->setMemberFromConfig(
+            'departmentLocationBase', 'General', 'departmentLocationBase',
+            "No departmentLocationBase defined in FincLibero.ini."
+        );
+
+        // get the URIs for the limitations that are marking an item that a hold can
+        // be placed on
+        $this->setMemberFromConfig(
+            'titleHoldLimitations',
+            'General',
+            'titleHoldLimitations',
+            "No Limitations defined for action titleHold.",
+            'titleHoldLimitations'
+        );
+
+        // get the pickUpLocationPatterns configured in ILS ini and being used for
+        // filtering limitations used for pickuplocations
+        $this->setMemberFromConfig(
+            'pickUpLocationPatterns',
+            'General',
+            'pickUpLocationPatterns',
+            "No pickUpLocationPatterns defined in FincLibero.ini."
+        );
+
+        // get the URIs for the limitations that are marking an item that a storage
+        // retrieval request can be placed on
+        $this->setMemberFromConfig(
+            'requestableLimitations',
+            'General',
+            'requestableLimitations',
+            "No Limitations defined for action Storage Retrieval Request.",
+            'requestableLimitations'
+        );
+
+        // get the URIs for the limitations that are marking an item that a hold can
+        // be placed on
+        $this->setMemberFromConfig(
+            'holdableLimitations',
+            'General',
+            'holdableLimitations',
+            "No Limitations defined for action Hold.",
+            'holdableLimitations'
+        );
+
+        // get the URIs for the limitations that are marking an item that a recall can
+        // be placed on
+        $this->setMemberFromConfig(
+            'recallableLimitations',
+            'General',
+            'recallableLimitations',
+            "No Limitations defined for action Recall.",
+            'recallableLimitations'
+        );
+
+        // get URIs of limitations that will identify the item for being bound to
+        // another item
+        $this->setMemberFromConfig(
+            'awlLimitations',
+            'General',
+            'awlLimitations',
+            "No awlLimitations defined.",
+            'awlLimitations'
+        );
+
+        // get the URIs identifying records for stack views
+        $this->setMemberFromConfig(
+            'stackURIs',
+            'General',
+            'stackURIs',
+            "No stack URIs defined."
+        );
+
+        // get the URIs identifying records for reading room views
+        $this->setMemberFromConfig(
+            'readingRoomURIs',
+            'General',
+            'readingRoomURIs',
+            "No reading room URIs defined."
+        );
+    }
+
+    /**
+     * Helper function for @see FincLibero::init()
+     * Set some member variables based on Config entries from FincILS.ini or
+     * FincLibero.ini.
+     * If the given config key (in given section) is set, the member variable will be
+     * initialized with the value(s) from that key.
+     * Otherwise (not set):
+     * When the setting is required via the [RequiredConfig] section AND a debug message is
+     * given, this message will be added to the debug log.
+     * With the last parameter $configuredLimitationName, the config entry will be added
+     * to the set of configured limitations under the given name
+     *
+     * @param string $memberName               name of class member to be set
+     * @param string $configSection            section from INI file to look up (e.g. 'General' for section [General])
+     * @param string $configKey                config key within that section
+     * @param string $debugMessage             message to add to debug log on fail if the config is set required
+     * @param string $configuredLimitationName name of limitation type to be used in setConfiguredLimitation()
+     */
+    protected function setMemberFromConfig(
+        $memberName,
+        $configSection,
+        $configKey,
+        $debugMessage = '',
+        $configuredLimitationName = ''
+    ) {
+        if (
+            !isset($this->config[$configSection])
+            ||
+            !isset($this->config[$configSection][$configKey])
+        ) {
+            if (
+                !empty($debugMessage)
+                &&
+                ($this->config['RequiredConfig'][$configSection][$configKey] ?? false)
+            ) {
+                throw new InitException($debugMessage);
+            }
+        } else {
+            $this->{$memberName} = $this->config[$configSection][$configKey];
+            if (!empty($configuredLimitationName)) {
+                $this->setConfiguredLimitation($configuredLimitationName);
+            }
+        }
     }
 
     /**
@@ -408,6 +537,28 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
         }
     }
 
+    /**
+     * FincLibero specific override: we need to manually update the cat_password in
+     * the session as we do not save passwords in the database and therefore the
+     * ILSAuthenticator will use the session data instead.
+     *
+     * @param array $details Array with patron information, newPassword and
+     *                       oldPassword.
+     *
+     * @return array An array with patron information.
+     */
+    public function changePassword($details)
+    {
+        $retval = parent::changePassword($details);
+
+        if ($retval == ['success' => true, 'status' => 'Successfully changed']) {
+            $session = $this->getSession();
+            $session->cat_password = $details['newPassword'];
+        }
+
+        return $retval;
+    }
+
     /**
      * PAIA helper function to map session data to return value of patronLogin()
      *
@@ -483,17 +634,7 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
     {
         $return = parent::getItemStatus($item);
 
-        // add all item specific information from DAIA field about to item_notes
-        // (https://intern.finc.info/issues/7863)
-        $about = (isset($item['about'])) ? [$item['about']] : [];
-
-        $return['item_notes'] = array_unique(
-            array_merge(
-                (array) $return['status'],
-                $return['item_notes'],
-                $about
-            )
-        );
+        $return['item_notes'] = $this->getItemNotes($return, $item);
 
         $return['awlRecordId'] = $this->getBoundItemId($item);
         // is this item bound with another item?
@@ -516,9 +657,49 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
             }
         }
 
+        $return['service_type'] = $this->_reduceServices($return['services']);
+
         return $return;
     }
 
+    /**
+     * Helper function for getItemStatus().
+     * Gather all item notes from current (parent) result and DAIA item
+     *
+     * @param array $return intermediate result from parent::getItemStatus()
+     * @param array $item   DAIA item
+     * @return array 'item_notes' part for result
+     */
+    protected function getItemNotes($return, $item)
+    {
+        // add all item specific information from DAIA field about to item_notes
+        // (https://intern.finc.info/issues/7863)
+        return array_unique(
+            array_merge(
+                (array)$return['status'],
+                $return['item_notes'],
+                $item['about'] ?? []
+            )
+        );
+    }
+
+    /**
+     * Helper function for 'service_type' entry in getItemStatus
+     *
+     * @param array $services actually present services in DAIA item
+     * @return false|int|mixed preferred service if present or 0
+     */
+    protected function _reduceServices($services)
+    {
+        $prio = [
+            'loan',
+            'presentation',
+        ];
+        $res = array_intersect($prio, $services);
+        if (empty($res)) return 0;
+        else return current($res);
+    }
+
     /**
      * Helper function to return an appropriate status string for current item
      *
@@ -731,6 +912,31 @@ class FincLibero extends FincILS implements TranslatorAwareInterface
         return $this->mapPaiaItems($items, 'myHoldsMapping');
     }
 
+    /**
+     * This method queries the ILS for a patron's current storage retrieval requests.
+     * Returns items with properties:
+     *      - document.status = 2 (ordered)
+     *      - document.storage and document.storageid do have a certain location (Magazin, but not Lesesaal)
+     *
+     * note: stackURIs musst be configured in FincLibero.ini as required
+     *
+     * @param array $patron Array returned from patronLogin()
+     *
+     * @return array
+     */
+    public function getMyStorageRetrievalRequests($patron)
+    {
+        $filter = [
+            'status'  => [2],
+            'storageid' => $this->stackURIs,
+            'regex'   => ['item' => "/^(" . $this->getDaiaIdPrefixNamespace() . ").*$/"]
+        ];
+        // get items-docs for given filters
+        $items = $this->paiaGetItems($patron, $filter);
+
+        return $this->mapPaiaItems($items, 'myStorageRetrievalRequestsMapping');
+    }
+
     /**
      * Customized getMyTransactions for FincLibero to return items with properties:
      *      - document.status = 3 (held)
diff --git a/module/finc/src/finc/ILS/InitException.php b/module/finc/src/finc/ILS/InitException.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e7a692cc70717ea3e4b107c33806966381ced22
--- /dev/null
+++ b/module/finc/src/finc/ILS/InitException.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Exception thrown in FincLibero::init()
+ * PHP version 7
+ *
+ * Copyright (C) Leipzig University Library 2021.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  ILS_Drivers
+ * @author   Dorian Merz <merz@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
+ */
+namespace finc\ILS;
+
+/**
+ * Exception thrown in FincLibero::init()
+ *
+ * @category VuFind
+ * @package  ILS_Drivers
+ * @author   Dorian Merz <merz@ub.uni-leipzig.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
+ */
+class InitException extends \Exception
+{
+}
diff --git a/module/finc/src/finc/View/Helper/Root/ExternalLink.php b/module/finc/src/finc/View/Helper/Root/ExternalLink.php
index 91b1df9feaadbd1b7b2d51291c99fbd5a88d46d9..6bfbb6de3ccd846cfaef56c46476b148458683ec 100644
--- a/module/finc/src/finc/View/Helper/Root/ExternalLink.php
+++ b/module/finc/src/finc/View/Helper/Root/ExternalLink.php
@@ -27,6 +27,9 @@
  */
 namespace finc\View\Helper\Root;
 
+use Zend\View\Helper\EscapeHtml;
+use Zend\View\Helper\EscapeHtmlAttr;
+
 /**
  * External link view helper
  *
@@ -38,13 +41,6 @@ namespace finc\View\Helper\Root;
  */
 class ExternalLink extends \Zend\View\Helper\AbstractHelper
 {
-    /**
-     * Context view helper
-     *
-     * @var \VuFind\View\Helper\Root\Translate
-     */
-    protected $translator;
-
     /**
      * Default html attributes for external links
      *
@@ -55,6 +51,13 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper
         'rel' => 'noopener'
     ];
 
+    /**
+     * Escape manuelly flag
+     *
+     * @var bool
+     */
+    protected $manualEscape;
+
     /**
      * Renders an anchor element (hyperlink) to an external website
      *
@@ -62,7 +65,8 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper
      * @param string $label          desired label or translation token
      * @param array  $attributes     more attributes to apply to the a element,
      *                               key-value-pairs
-     * @param bool   $translateLabel true if label shall be translated
+     * @param bool   $manualEscape   true if $label conatins html to display and
+     *                               $attributes are escped before calling method
      *
      * @return string
      */
@@ -70,36 +74,55 @@ class ExternalLink extends \Zend\View\Helper\AbstractHelper
         $href,
         $label = '',
         $attributes = [],
-        $translateLabel = false
+        $manualEscape = false
     ) {
+        $this->manualEscape = $manualEscape;
+
         $link = '<a href="' . $href . '"';
         $attributes = array_merge($this->defaultAttributes, $attributes);
         foreach ($attributes as $key => $value) {
-            $link .= ' ' . $key . '="' . $value . '"';
+            $link .= ' ' . $key . '="' . $this->escapeHtmlAttr($value) . '"';
         }
         $link .= '>';
         if (empty($label)) {
             $label = $href;
-        } elseif ($translateLabel) {
-            $label = $this->translate($label);
         }
-        $link .= $label . '</a>';
+        $link .= $this->escapeHtml($label) . '</a>';
 
         return $link;
     }
 
     /**
-     * Helper function to provide access to Translate ViewHelper
+     * Helper function to provide access to EscapeHTML ViewHelper
+     * and esacpe lable if not disabled
+     *
+     * @param string $html html to escape
+     *
+     * @return string
+     */
+    protected function escapeHtml($html)
+    {
+        if ($this->manualEscape) {
+            return $html;
+        }
+        $htmlEscaper = $this->getView()->plugin('escapeHtml');
+        return $htmlEscaper($html);
+    }
+
+    /**
+     * Helper function to provide access to EscaperHTML Attribute ViewHelper
+     * and esacpe HTML attributes if not disabled
      *
      * @param string $label translation token
      *
      * @return string
      */
-    protected function translate($label)
+    protected function escapeHtmlAttr($htmlAttr)
     {
-        if (!isset($this->translator)) {
-            $this->translator = $this->getView()->plugin('translate');
+        if ($this->manualEscape) {
+            return $htmlAttr;
         }
-        return $this->translator->translate($label);
+        $htmlAttrEscaper = $this->getView()->plugin('escapeHtmlAttr');
+        return $htmlAttrEscaper($htmlAttr);
     }
 }
diff --git a/themes/finc-accessibility/js/record.js b/themes/finc-accessibility/js/record.js
new file mode 100644
index 0000000000000000000000000000000000000000..9766867212c7524e3ed39a0a6eac7c250fc27d76
--- /dev/null
+++ b/themes/finc-accessibility/js/record.js
@@ -0,0 +1,323 @@
+/*global deparam, getUrlRoot, grecaptcha, recaptchaOnLoad, resetCaptcha, syn_get_widget, userIsLoggedIn, VuFind, setupJumpMenus */
+/*exported ajaxTagUpdate, recordDocReady, refreshTagListCallback */
+
+/**
+ * Functions and event handlers specific to record pages.
+ */
+function checkRequestIsValid(element, requestType) {
+  var recordId = element.href.match(/\/Record\/([^/]+)\//)[1];
+  var vars = deparam(element.href);
+  vars.id = recordId;
+
+  var url = VuFind.path + '/AJAX/JSON?' + $.param({
+    method: 'checkRequestIsValid',
+    id: recordId,
+    requestType: requestType,
+    data: vars
+  });
+  $.ajax({
+    dataType: 'json',
+    cache: false,
+    url: url
+  })
+    .done(function checkValidDone(response) {
+      if (response.data.status) {
+        $(element).removeClass('disabled')
+          .attr('title', response.data.msg)
+          .html('<i class="fa fa-flag" aria-hidden="true"></i>&nbsp;' + response.data.msg);
+      } else {
+        $(element).remove();
+      }
+    })
+    .fail(function checkValidFail(/*response*/) {
+      $(element).remove();
+    });
+}
+
+function setUpCheckRequest() {
+  $('.checkRequest').each(function checkRequest() {
+    checkRequestIsValid(this, 'Hold');
+  });
+  $('.checkStorageRetrievalRequest').each(function checkStorageRetrievalRequest() {
+    checkRequestIsValid(this, 'StorageRetrievalRequest');
+  });
+  $('.checkILLRequest').each(function checkILLRequest() {
+    checkRequestIsValid(this, 'ILLRequest');
+  });
+}
+
+function deleteRecordComment(element, recordId, recordSource, commentId) {
+  var url = VuFind.path + '/AJAX/JSON?' + $.param({ method: 'deleteRecordComment', id: commentId });
+  $.ajax({
+    dataType: 'json',
+    url: url
+  })
+    .done(function deleteCommentDone(/*response*/) {
+      $($(element).closest('.comment')[0]).remove();
+    });
+}
+
+function refreshCommentList($target, recordId, recordSource) {
+  var url = VuFind.path + '/AJAX/JSON?' + $.param({
+    method: 'getRecordCommentsAsHTML',
+    id: recordId,
+    source: recordSource
+  });
+  $.ajax({
+    dataType: 'json',
+    url: url
+  })
+    .done(function refreshCommentListDone(response) {
+      // Update HTML
+      var $commentList = $target.find('.comment-list');
+      $commentList.empty();
+      $commentList.append(response.data.html);
+      $commentList.find('.delete').unbind('click').click(function commentRefreshDeleteClick() {
+        var commentId = $(this).attr('id').substr('recordComment'.length);
+        deleteRecordComment(this, recordId, recordSource, commentId);
+        return false;
+      });
+      $target.find('.comment-form input[type="submit"]').button('reset');
+      resetCaptcha($target);
+    });
+}
+
+function registerAjaxCommentRecord(_context) {
+  var context = typeof _context === "undefined" ? document : _context;
+  // Form submission
+  $(context).find('form.comment-form').unbind('submit').submit(function commentFormSubmit() {
+    var form = this;
+    var id = form.id.value;
+    var recordSource = form.source.value;
+    var url = VuFind.path + '/AJAX/JSON?' + $.param({ method: 'commentRecord' });
+    var data = {
+      comment: form.comment.value,
+      id: id,
+      source: recordSource
+    };
+    if (typeof grecaptcha !== 'undefined') {
+      var recaptcha = $(form).find('.g-recaptcha');
+      if (recaptcha.length > 0) {
+        data['g-recaptcha-response'] = grecaptcha.getResponse(recaptcha.data('captchaId'));
+      }
+    }
+    $.ajax({
+      type: 'POST',
+      url: url,
+      data: data,
+      dataType: 'json'
+    })
+      .done(function addCommentDone(/*response, textStatus*/) {
+        var $form = $(form);
+        var $tab = $form.closest('.list-tab-content');
+        if (!$tab.length) {
+          $tab = $form.closest('.tab-pane');
+        }
+        refreshCommentList($tab, id, recordSource);
+        $form.find('textarea[name="comment"]').val('');
+        $form.find('input[type="submit"]').button('loading');
+        resetCaptcha($form);
+      })
+      .fail(function addCommentFail(response, textStatus) {
+        if (textStatus === 'abort' || typeof response.responseJSON === 'undefined') { return; }
+        VuFind.lightbox.alert(response.responseJSON.data, 'danger');
+      });
+    return false;
+  });
+  // Delete links
+  $('.delete').click(function commentDeleteClick() {
+    var commentId = this.id.substr('recordComment'.length);
+    deleteRecordComment(this, $('.hiddenId').val(), $('.hiddenSource').val(), commentId);
+    return false;
+  });
+  // Prevent form submit
+  return false;
+}
+
+function registerTabEvents() {
+  // Logged in AJAX
+  registerAjaxCommentRecord();
+  // Render recaptcha
+  recaptchaOnLoad();
+
+  setUpCheckRequest();
+
+  VuFind.lightbox.bind('.tab-pane.active');
+}
+
+function removeHashFromLocation() {
+  if (window.history.replaceState) {
+    var href = window.location.href.split('#');
+    window.history.replaceState({}, document.title, href[0]);
+  } else {
+    window.location.hash = '#';
+  }
+}
+
+function ajaxLoadTab($newTab, tabid, setHash) {
+  // Request the tab via AJAX:
+  $.ajax({
+    url: VuFind.path + getUrlRoot(document.URL) + '/AjaxTab',
+    type: 'POST',
+    data: {tab: tabid}
+  })
+    .always(function ajaxLoadTabDone(data) {
+      if (typeof data === 'object') {
+        $newTab.html(data.responseText ? data.responseText : VuFind.translate('error_occurred'));
+      } else {
+        $newTab.html(data);
+      }
+      registerTabEvents();
+      if (typeof syn_get_widget === "function") {
+        syn_get_widget();
+      }
+      if (typeof setHash == 'undefined' || setHash) {
+        window.location.hash = tabid;
+      } else {
+        removeHashFromLocation();
+      }
+      setupJumpMenus($newTab);
+    });
+  return false;
+}
+
+function refreshTagList(_target, _loggedin) {
+  var loggedin = !!_loggedin || userIsLoggedIn;
+  var target = _target || document;
+  var recordId = $(target).find('.hiddenId').val();
+  var recordSource = $(target).find('.hiddenSource').val();
+  var $tagList = $(target).find('.tagList');
+  if ($tagList.length > 0) {
+    var url = VuFind.path + '/AJAX/JSON?' + $.param({
+      method: 'getRecordTags',
+      id: recordId,
+      source: recordSource
+    });
+    $.ajax({
+      dataType: 'json',
+      url: url
+    })
+      .done(function getRecordTagsDone(response) {
+        $tagList.empty();
+        $tagList.replaceWith(response.data.html);
+        if (loggedin) {
+          $tagList.addClass('loggedin');
+        } else {
+          $tagList.removeClass('loggedin');
+        }
+      });
+  }
+}
+function refreshTagListCallback() {
+  refreshTagList(false, true);
+}
+
+function ajaxTagUpdate(_link, tag, _remove) {
+  var link = _link || document;
+  var remove = _remove || false;
+  var $target = $(link).closest('.record');
+  var recordId = $target.find('.hiddenId').val();
+  var recordSource = $target.find('.hiddenSource').val();
+  $.ajax({
+    url: VuFind.path + '/AJAX/JSON?method=tagRecord',
+    method: 'POST',
+    data: {
+      tag: '"' + tag.replace(/\+/g, ' ') + '"',
+      id: recordId,
+      source: recordSource,
+      remove: remove
+    }
+  })
+    .always(function tagRecordAlways() {
+      refreshTagList($target, false);
+    });
+}
+
+function getNewRecordTab(tabid) {
+  return $('<div class="tab-pane ' + tabid + '-tab" id="' + tabid + '" role="tabpanel" tabindex="-1" aria-labelledby="' + tabid + '-tabselector"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> ' + VuFind.translate('loading') + '...</div>');
+}
+
+function backgroundLoadTab(tabid) {
+  if ($('.' + tabid + '-tab').length > 0) {
+    return;
+  }
+  var newTab = getNewRecordTab(tabid);
+  $('.nav-tabs a.' + tabid).closest('.result,.record').find('.tab-content').append(newTab);
+  return ajaxLoadTab(newTab, tabid, false);
+}
+
+function applyRecordTabHash() {
+  var activeTab = $('.record-tabs li.active').attr('data-tab');
+  var $initiallyActiveTab = $('.record-tabs li.initiallyActive a');
+  var newTab = typeof window.location.hash !== 'undefined'
+    ? window.location.hash.toLowerCase() : '';
+
+  // Open tab in url hash
+  if (newTab.length <= 1 || newTab === '#tabnav') {
+    $initiallyActiveTab.click();
+  } else if (newTab.length > 1 && '#' + activeTab !== newTab) {
+    $('.' + newTab.substr(1) + ' a').click();
+  }
+}
+
+$(window).on('hashchange', applyRecordTabHash);
+
+function recordDocReady() {
+  $('.record-tabs .nav-tabs a').click(function recordTabsClick() {
+    var $li = $(this).parent();
+    // If it's an active tab, click again to follow to a shareable link.
+    if ($li.hasClass('active')) {
+      return true;
+    }
+    var tabid = $li.attr('data-tab');
+    var $top = $(this).closest('.record-tabs');
+
+    // accessibility: mark tab controls as selected
+    $top.find('.record-tab.active').find('a').attr('aria-selected', 'false');
+    $('#' + tabid + '-tabselector').attr('aria-selected', 'true').attr('aria-controls', tabid);
+
+    // accessibility: set aria-hidden for content panes
+    $top.find('.tab-pane.active').removeClass('active').attr('aria-hidden', 'true');
+    $top.find('.' + tabid + '-tab').addClass('active').attr('aria-hidden', 'false');
+
+    // if we're flagged to skip AJAX for this tab, we need special behavior:
+    if ($li.hasClass('noajax')) {
+      // if this was the initially active tab, we have moved away from it and
+      // now need to return -- just switch it back on.
+      if ($li.hasClass('initiallyActive')) {
+        $(this).tab('show');
+        window.location.hash = 'tabnav';
+        return false;
+      }
+      // otherwise, we need to let the browser follow the link:
+      return true;
+    }
+    $(this).tab('show');
+    if ($top.find('.' + tabid + '-tab').length > 0) {
+      $top.find('.' + tabid + '-tab').addClass('active');
+      if ($top.find('#' + tabid ).length) {
+        $top.find('#' + tabid ).parent().focus();
+      }
+      if ($(this).parent().hasClass('initiallyActive')) {
+        removeHashFromLocation();
+      } else {
+        window.location.hash = tabid;
+      }
+      return false;
+    } else {
+      var newTab = getNewRecordTab(tabid).addClass('active');
+      $top.find('.tab-content').append(newTab);
+      if ($top.find('#' + tabid ).length) {
+        $top.find('#' + tabid ).parent().focus();
+      }
+      return ajaxLoadTab(newTab, tabid, !$(this).parent().hasClass('initiallyActive'));
+    }
+  });
+
+  $('[data-background]').each(function setupBackgroundTabs(index, el) {
+    backgroundLoadTab(el.className);
+  });
+
+  registerTabEvents();
+  applyRecordTabHash();
+}
diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js
index 43f5fe0175e2f59b7456f6ae7d5439be1dc7a39e..0b23dca9f44e502f5c348a865c348630a554e075 100644
--- a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js
+++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-de.min.js
@@ -310,4 +310,21 @@
             k = e.which || e.keyCode;
         /(37|38|39|40)/.test(k) && (index = $tabs.index($tabs.filter(".active")), (37 == k || 38 == k) && (index--, selectTab(index)), (39 == k || 40 == k) && (index++, selectTab(index)), e.preventDefault(), e.stopPropagation())
     }, $(document).on("keydown.carousel.data-api", "li[role=tab]", $.fn.carousel.Constructor.prototype.keydown)
-}(jQuery);
\ No newline at end of file
+}(jQuery);
+
+$(document).on('keydown', '.dropdown-abort', function(e) {
+    if(e.which == 13) {
+        let toggle = $(this).parent().parent().parent().find('.dropdown-toggle');
+        if(toggle.not(this).length){
+            toggle.focus();
+        }
+    }
+});
+$(document).on('keyup', '.dropdown-abort', function(e) {
+    if(e.which == 13) {
+        let toggle = $(this).parent().parent().parent().find('.dropdown-toggle');
+        if(toggle.not(this).length){
+            toggle.dropdown("toggle");
+        }
+    }
+});
\ No newline at end of file
diff --git a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js
index 630f112aed7e6a0149687a95215c623908bf0404..11bef8e1ba5fde3b9db5fe1c6b18a7239e3b23a1 100644
--- a/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js
+++ b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js
@@ -3,7 +3,6 @@
 * Copyright (c) 2020 PayPal Accessibility Team; Licensed BSD */
 !function ($) {
     "use strict";
-    console.log('en');
     var uniqueId = function (prefix) {
         return (prefix || "ui-id") + "-" + Math.floor(1e3 * Math.random() + 1)
     }, focusable = function (element, isTabIndexNotNaN) {
@@ -311,4 +310,21 @@
             k = e.which || e.keyCode;
         /(37|38|39|40)/.test(k) && (index = $tabs.index($tabs.filter(".active")), (37 == k || 38 == k) && (index--, selectTab(index)), (39 == k || 40 == k) && (index++, selectTab(index)), e.preventDefault(), e.stopPropagation())
     }, $(document).on("keydown.carousel.data-api", "li[role=tab]", $.fn.carousel.Constructor.prototype.keydown)
-}(jQuery);
\ No newline at end of file
+}(jQuery);
+
+$(document).on('keydown', '.dropdown-abort', function(e) {
+    if(e.which == 13) {
+        let toggle = $(this).parent().parent().parent().find('.dropdown-toggle');
+        if(toggle.not(this).length){
+            toggle.focus();
+        }
+    }
+});
+$(document).on('keyup', '.dropdown-abort', function(e) {
+    if(e.which == 13) {
+        let toggle = $(this).parent().parent().parent().find('.dropdown-toggle');
+        if(toggle.not(this).length){
+            toggle.dropdown("toggle");
+        }
+    }
+});
\ No newline at end of file
diff --git a/themes/finc-accessibility/scss/compiled.scss b/themes/finc-accessibility/scss/compiled.scss
index 766002b57789f053a07aea5a5099e79b2f515732..db481aca78be66dffc9e30ba18c9a77e01996dbd 100644
--- a/themes/finc-accessibility/scss/compiled.scss
+++ b/themes/finc-accessibility/scss/compiled.scss
@@ -54,4 +54,10 @@
 a.remove-filter {
   display: flex;
   width: 100%;
+}
+
+.record-tab.active{
+  .load-tab-content {
+    display: none;
+  }
 }
\ No newline at end of file
diff --git a/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml b/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml
index 293af05732827251527ee22b281cc69fe25b3cae..1b1f8adcbba51d11ecdae3e1785993b1e8cfff98 100644
--- a/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml
+++ b/themes/finc-accessibility/templates/Recommend/SideFacets/cluster-list.phtml
@@ -30,7 +30,7 @@
   ]) ?>
 <?php endforeach; ?>
 <?php if (empty($this->cluster['list'])): ?>
-  <div class="facet"><?=$this->transEsc('facet_list_empty')?></div>
+  <span class="facet"><?=$this->transEsc('facet_list_empty')?></span>
 <?php endif; ?>
 
 <?php /* LESS and SEE MORE links */ ?>
diff --git a/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml b/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml
index 3957f6ba12e6ac722e65ea77266a15df592e7e73..3d2c763fce3e185c1e9f000fd9d06f4856a12c75 100644
--- a/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml
+++ b/themes/finc-accessibility/templates/Recommend/SideFacets/filter-list.phtml
@@ -1,7 +1,7 @@
 <!-- finc-accessibility - Recommend - SideFacets - filter-list.phtml -->
 <?php /* #18509 copied from bootstrap for adding language tags to displayText */ ?>
 <div class="facet-group active-filters">
-  <div class="title"><?=$this->transEsc('Remove Filters')?> <span class="sr-only"><?=$this->transEsc('facet_deselect_hint') ?></div>
+  <div class="title"><?=$this->transEsc('Remove Filters')?> <span class="sr-only"><?=$this->transEsc('facet_deselect_hint') ?></span></div>
   <ul>
   <?php foreach ($filterList as $field => $filters): ?>
     <?php foreach ($filters as $i => $filter): ?>
diff --git a/themes/finc/js/lightbox.js b/themes/finc/js/lightbox.js
index 4d183e226b88d770ccea6bb32f1779abf431f7cd..8cdfbe87fe9d0cec2d198dd47dc4231c84df65ca 100644
--- a/themes/finc/js/lightbox.js
+++ b/themes/finc/js/lightbox.js
@@ -114,6 +114,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();
   }
@@ -426,6 +436,9 @@ VuFind.register('lightbox', function Lightbox() {
       }
     }
   }
+  function setOrigin(origin) {
+    _origin = origin;
+  }
   function onKeydown(e) {
     if (event.keyCode === 27) { // esc
       close();
@@ -483,7 +496,6 @@ VuFind.register('lightbox', function Lightbox() {
       });
     });
   }
-
   function reset() {
     _html(VuFind.translate('loading') + '...');
     _originalUrl = false;
@@ -540,6 +552,7 @@ VuFind.register('lightbox', function Lightbox() {
     render: render,
     // Reset
     reset: reset,
+    setOrigin: setOrigin,
     // Init
     init: init
   };
diff --git a/themes/finc/templates/Recommend/EbscoResults.phtml b/themes/finc/templates/Recommend/EbscoResults.phtml
index 2801644566d2ab48ed2af231dea8c7941f3daf61..253b18944ffacebbbb4811444c50dd5308a4b9f8 100644
--- a/themes/finc/templates/Recommend/EbscoResults.phtml
+++ b/themes/finc/templates/Recommend/EbscoResults.phtml
@@ -13,15 +13,17 @@ if (is_array($data['results']) && count($data['results']) > 0): ?>
         <span class="badge">
                 <?= $this->escapeHtml($result['hits']) ?>
             </span>
-            <a href="<?= $this->escapeHtmlAttr($result['url']) ?>" target="_blank">
-                <?= $this->escapeHtml($this->truncate($this->transEsc('Ebsco::'.$result['database']), 90)) ?>
-            </a>
+        <?= $this->externalLink(
+              $this->escapeHtmlAttr($result['url']),
+              $this->truncate($this->translate('Ebsco::'.$result['database']), 90)
+            ) ?>
       </div>
         <?php endforeach; ?>
-        <a class="facet narrow-toggle" href="<?=$this->escapeHtmlAttr($data['hits_total_url'])?>" target="_blank" rel="nofollow">
-          <?=$this->transEsc('more')?>&nbsp;...
-            </a>
-
+      <?= $this->externalLink(
+            $data['hits_total_url'],
+            $this->translate('more'),
+            ['class' => 'facet narrow-toggle']
+          ) ?>
       </div>
   </div>
 <?php endif; ?>
diff --git a/themes/finc/templates/Recommend/SideFacets.phtml b/themes/finc/templates/Recommend/SideFacets.phtml
index 8c7fff5acfa902339f2eaf28c3f5376178d00a96..3e524b476825eedf817ba8fa2c8b110c3aadf186 100644
--- a/themes/finc/templates/Recommend/SideFacets.phtml
+++ b/themes/finc/templates/Recommend/SideFacets.phtml
@@ -55,9 +55,9 @@ if ($hierarchicalFacets) {
 <?php if (!empty($sideFacetSet) && $results->getResultTotal() > 0): ?>
   <?php foreach ($sideFacetSet as $title => $cluster): ?>
     <div class="facet-group" id="side-panel-<?=$this->escapeHtmlAttr($title)?>">
-      <button <?php if(in_array($title, $collapsedFacets)): ?>class="title collapsed" aria-expanded="false"<?php else: ?>class="title" aria-expanded="true"<?php endif ?> data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" >
+      <a <?php if(in_array($title, $collapsedFacets)): ?>class="title collapsed" aria-expanded="false"<?php else: ?>class="title" aria-expanded="true"<?php endif ?> data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" >
         <?=$this->transEsc($cluster['label'])?> <span class="sr-only"><?=$this->transEsc('facet_select_hint') ?></span>
-      </button>
+      </a>
       <ul id="side-collapse-<?=$this->escapeHtmlAttr($title)?>" class="collapse<?php if (!in_array($title, $collapsedFacets)): ?> in<?php endif ?>">
         <?=$this->context($this)->renderInContext(
           'Recommend/SideFacets/facet.phtml',
diff --git a/themes/finc/templates/Recommend/SideFacets/range-slider.phtml b/themes/finc/templates/Recommend/SideFacets/range-slider.phtml
index 430be23580f50ce2ba0fccf96c7bb22bdf494db6..31a94bfef9261342ea71876881dca013bad5985f 100644
--- a/themes/finc/templates/Recommend/SideFacets/range-slider.phtml
+++ b/themes/finc/templates/Recommend/SideFacets/range-slider.phtml
@@ -4,7 +4,7 @@
      * slider-container element
      */
 ?>
-<div class="facet">
+<li class="facet">
   <?php if (!empty($this->facet['values'][0])): ?>
     <?php $this->sideFacet()->setAppliedFacet($this->transEsc('Skip to facet', ['%%filter_name%%' => $this->transEsc('Year of Publication')]), $this->escapeHtmlAttr($this->title) . 'from')?>
   <?php elseif (!empty($this->facet['values'][1])): ?>
@@ -34,7 +34,6 @@
     <?php endif; ?>
     <input class="btn btn-default" type="submit" value="<?=$this->transEsc('Set')?>"/>
   </form>
-</div>
 <?php if ($this->facet['type'] == 'date'): ?>
   <?php $this->headScript()->appendFile('vendor/bootstrap-slider.min.js'); ?>
   <?php $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); ?>
@@ -83,4 +82,5 @@ JS;
   ?>
   <?=$this->inlineScript(\Zend\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?>
 <?php endif; ?>
+</li>
 <!-- finc - recommend - sidefacets - rangeslider - END -->
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml
index 77bed61f58f3b6a7a2cfc3113ec6a2fbb194be64..30507b95f9bba7a18fa8cdd5d980856dfd166994 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/link-isn.phtml
@@ -1,9 +1,9 @@
 <?php
 /* use advanced search if we have multiple issns */
 if (is_array($this->lookfor) && count($this->lookfor) > 1) {
-  $query = '?join=AND&amp;bool0[]=OR';
+  $query = '?join=AND&amp;bool0%5B%5D=OR';
   foreach ($this->lookfor as $issn) {
-    $query .= '&amp;lookfor0[]=' . urlencode($issn) . '&amp;type0[]=ISN';
+    $query .= '&amp;lookfor0%5B%5D=' . urlencode($issn) . '&amp;type0%5B%5D=ISN';
   }
 } elseif (count($this->lookfor) == 1) {
   $query = '?lookfor=%22' . urlencode($this->lookfor[0]) . '%22&amp;type=ISN';
diff --git a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
index 377e7ddf6a32d81a0bc92b694099ffb1713e1234..a816ef7b34e942e2e74fc35ea7393c004298a462 100644
--- a/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
+++ b/themes/finc/templates/RecordDriver/DefaultRecord/list-entry.phtml
@@ -224,7 +224,13 @@ $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list'
             <ul class="dropdown-menu" role="menu" aria-labelledby="<?= $dLabel ?>">
               <li>
                 <?php /* #17711 give user feedback and dont reload page after deleting */ ?>
-                <a href="javascript:document.getElementById('<?=$dLabel?>').focus();" title="<?= $this->transEsc('confirm_delete_brief') ?>" onClick="$.post(
+                <a href="javascript:document.getElementById('<?=$dLabel?>').focus();" title="<?= $this->transEsc('confirm_delete_brief') ?>" onClick="
+                  let next = $(this).closest('.result.ajaxItem').next('.result.ajaxItem').find('.del-button');
+                  if (next.length === 0) {
+                    next = $('[id^=delete_list_items_]').first();
+                  }
+                  VuFind.lightbox.setOrigin(next);
+                  $.post(
                   '<?= $deleteUrl ?>',
                   {
                     'delete':'<?= $this->escapeJs($id) ?>',
diff --git a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
index e21b80019c88987fa4b300e1455720c988b77584..63c4ed5c15e04155f7917b395ef565e2dd4f658b 100644
--- a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
+++ b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml
@@ -4,6 +4,7 @@ $coverDetails = $this->record($this->driver)->getCoverDetails('result-list', 'me
 $cover = $coverDetails['html'];
 $thumbnail = false;
 $thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('result');
+$describedById = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId();
 if ($cover):
   ob_start(); ?>
   <div class="media-<?=$thumbnailAlignment?> <?=$this->escapeHtmlAttr($coverDetails['size'])?>" aria-hidden="true">
@@ -31,7 +32,7 @@ if ($cover):
   <div class="media-body">
     <div class="result-body">
       <div>
-        <a href="<?=$this->recordLink()->getUrl($this->driver)?>" class="title getFull" data-view="<?=$this->params->getOptions()->getListViewOption()?>" lang="">
+        <a id="<?=$describedById?>" href="<?=$this->recordLink()->getUrl($this->driver)?>" class="title getFull" data-view="<?=$this->params->getOptions()->getListViewOption()?>" lang="">
           <?=$this->record($this->driver)->getTitleHtml()?>
         </a>
       </div>
diff --git a/themes/finc/templates/RecordTab/acquisitionpda.phtml b/themes/finc/templates/RecordTab/acquisitionpda.phtml
index 91296ba15fa006a007013d4545a6cbfcb675aa6f..e069017a899ee0ec5596fda3bb820e5e9df2dbb3 100644
--- a/themes/finc/templates/RecordTab/acquisitionpda.phtml
+++ b/themes/finc/templates/RecordTab/acquisitionpda.phtml
@@ -15,9 +15,13 @@ $controllerClass = 'controller:SolrMarcFincPDA';
 <p class="alert alert-info"><?=$this->transEsc('PDA::pda_restriction_text')?></p>
 <div class="btn-group">
   <?php /* Leave title in here - it is used for the tooltip! - CK */ ?>
-  <a class="btn btn-primary" data-toggle="tooltip" title="<?=$this->transEsc('PDA::pda_open_new_window')?>" href="<?=$this->interlibraryloan()->getSwbLink($this->driver)?>" target="_blank">
-    <?=$this->transEsc('PDA::pda_tab_interlibrary_button')?>
-  </a>
+  <?= $this->externalLink(
+        $this->interlibraryloan()->getSwbLink($this->driver),
+        $this->translate('PDA::pda_tab_interlibrary_button'),
+        [ 'class' => 'btn btn-primary',
+          'data-toggle' => 'tooltip',
+          'title' => $this->translate('PDA::pda_open_new_window')]
+      ) ?>
   <a class="btn btn-primary pda-button <?=$controllerClass?>" data-lightbox href="<?=$this->url('record-pda', array('id' => $id))?>" rel="nofollow">
     <?=$this->transEsc('PDA::pda_tab_order_button')?>
   </a>
diff --git a/themes/finc/templates/RecordTab/holdingsils.phtml b/themes/finc/templates/RecordTab/holdingsils.phtml
index d929e6b59b9855a214f6df8fa0c0852f307a6d1e..ed99f581cf84e3bd1f396fba374371e32315551f 100644
--- a/themes/finc/templates/RecordTab/holdingsils.phtml
+++ b/themes/finc/templates/RecordTab/holdingsils.phtml
@@ -65,7 +65,7 @@ if (!empty($holdingTitleHold)): ?>
   <h2><?=$this->transEsc("Internet")?></h2>
   <?php if (!empty($urls)): ?>
     <?php foreach ($urls as $current): ?>
-      <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>"><?=$this->escapeHtml($current['desc'])?></a><br/>
+      <?= $this->externalLink($this->escapeHtmlAttr($this->proxyUrl($current['url'])), $current['desc']) ?><br/>
     <?php endforeach; ?>
   <?php endif; ?>
   <?php /* finc-specific snippet - #9274 - replaces if ($openUrlActive): ... - CK */ ?>
@@ -75,7 +75,7 @@ if (!empty($holdingTitleHold)): ?>
       if (!empty($fallbackUrls)): ?>
         <span id="urlsHideable" style="display: none">
           <?php foreach ($fallbackUrls as $current): ?>
-            <a href="<?=$this->escapeHtmlAttr($this->proxyUrl($current['url']))?>" target="_blank"><?=$this->escapeHtml($current['desc'] ?? $current['url'])?></a><br/>
+            <?= $this->externalLink($this->escapeHtmlAttr($this->proxyUrl($current['url'])), $current['desc'] ?? $current['url']) ?><br/>
           <?php endforeach; ?>
         </span>
       <?php endif; ?>
@@ -93,7 +93,7 @@ if (!empty($holdingTitleHold)): ?>
   <h2>
     <?php $locationText = $this->transEsc('location_' . $holding['location'], [], $holding['location']); ?>
     <?php if (isset($holding['locationhref']) && $holding['locationhref']): ?>
-      <a href="<?=$holding['locationhref']?>" target="_blank"><?=$locationText?></a>
+      <?= $this->externalLink($holding['locationhref'], $locationText) ?>
     <?php else: ?>
       <?=$locationText?>
     <?php endif; ?>
diff --git a/themes/finc/templates/ajax/resolverLinks.phtml b/themes/finc/templates/ajax/resolverLinks.phtml
index d9b82875c17cd7e6184f08cc5721db62b1dcad85..cdc5ac85facdc8c80928de7ad8b60646db73edb8 100644
--- a/themes/finc/templates/ajax/resolverLinks.phtml
+++ b/themes/finc/templates/ajax/resolverLinks.phtml
@@ -22,7 +22,12 @@
               <?php /* finc-specific change #7986 - END */ ?>
               <a href="<?=$this->escapeHtmlAttr($link['href'])?>" title="<?=isset($link['service_type'])?$this->escapeHtmlAttr($link['service_type']):''?>"<?=!empty($link['access'])?' class="access-'.$link['access'].'"':''?>><?=isset($link['title'])?$this->escapeHtml($link['title']):''?></a> <br />
               <?php /* finc-specific change #5334 - CK */ ?>
-              <small><?=isset($link['coverage'])?$this->escapeHtml($link['coverage']):''?><?=isset($link['coverageHref'])?' <a href="'.$link['coverageHref'].'" target="_blank">'.$this->translate('Readme').'</a>':''?></small>
+              <small>
+                <?= isset($link['coverage']) ? $this->escapeHtml($link['coverage']) : '' ?>
+                <?= isset($link['coverageHref'])
+                      ? $this->externalLink($link['coverageHref'], $this->translate('Readme'))
+                      : '' ?>
+              </small>
               <?php /* finc-specific change #5334 - END */ ?>
             <?php else: ?>
               <?=isset($link['title'])?$this->escapeHtml($link['title']):''?> <?=isset($link['coverage'])?$this->transEsc($link['coverage']):''?>
diff --git a/themes/finc/templates/ajax/status-full.phtml b/themes/finc/templates/ajax/status-full.phtml
index 303067afd3f54d49ba871af84d7028fc758dcb9f..f48411b6a7613839ee6b963ee2510646bc2053be 100644
--- a/themes/finc/templates/ajax/status-full.phtml
+++ b/themes/finc/templates/ajax/status-full.phtml
@@ -12,7 +12,7 @@
       <td data-title="<?= $this->transEsc('Location') ?>:" class="fullLocation">
         <?php $locationText = $this->transEsc('location_' . $item['location'], [], $item['location']); ?>
         <?php if (isset($item['locationhref']) && $item['locationhref']): ?>
-          <a href="<?=$item['locationhref']?>" target="_blank"><?=$locationText?></a>
+          <?= $this->externalLink($item['locationhref'], $locationText) ?>
         <?php else: ?>
           <?=$locationText?>
         <?php endif; ?>
diff --git a/themes/finc/templates/amsl/sources-list.phtml b/themes/finc/templates/amsl/sources-list.phtml
index 371f906684de2b40f1af54bc0857935c9662358e..8970d0d041d4de5d3bbc0838e7a59a97c667e406 100644
--- a/themes/finc/templates/amsl/sources-list.phtml
+++ b/themes/finc/templates/amsl/sources-list.phtml
@@ -49,9 +49,11 @@ $this->layout()->breadcrumbs .= '</li> <li class="active">' . $this->transEsc('L
             <?php foreach ($source as $sub_label => $collection): ?>
               <li>
                 <?php if (!empty($collection['href'])): ?>
-                  <a title="<?=$this->transEsc("Search For")?> <?=$sub_label?>" href='<?=$collection["href"]?>' target="_blank">
-                    <?=$sub_label?>
-                  </a>
+                <?= $this->externalLink(
+                      $collection["href"],
+                      $sub_label,
+                      ['title' => "{$this->transEsc("Search For")} {$sub_label}"]
+                    ) ?>
                 <?php else: ?>
                   <div tabindex="0" aria-label="<?=$this->transEsc("Source Title")?>">
                       <?=$sub_label?>
@@ -77,8 +79,12 @@ $this->layout()->breadcrumbs .= '</li> <li class="active">' . $this->transEsc('L
       <span>
         <?=$this->transEsc('support_by_dfg');?>
       </span>
-      <a href='http://www.dfg.de' target='_blank'>
-        <img src='<?=$this->imageLink('dfg_logo_text.png')?>' alt='Deutsche Forschungsgemeinschaft, DFG'>
+      <?= $this->externalLink(
+            'http://www.dfg.de',
+            "<img src='{$this->imageLink('dfg_logo_text.png')}' alt='Deutsche Forschungsgemeinschaft, DFG'>",
+            [],
+            true
+          ) ?>
       </a>
     </div>
   </div>
diff --git a/themes/finc/templates/footer.phtml b/themes/finc/templates/footer.phtml
index 301b47e88a484d27332358c9a3aea934023687ec..719d70099b343d088d6db736236e067b6fa92110 100644
--- a/themes/finc/templates/footer.phtml
+++ b/themes/finc/templates/footer.phtml
@@ -37,8 +37,8 @@
         <span>
           <?= $this->transEsc("Footer-Powered-By-Text") ?>
         </span>
-        <?= $this->externalLink("https://vufind.org", '<img src="' . $this->imageLink('vufind_logo.png') . '" alt="' . $this->translate('vufind-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('vufind-logo_title')]) ?>
-        <?= $this->externalLink("https://finc.info", '<img src="' . $this->imageLink('finc_logo.png') . '" alt="' . $this->translate('finc-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('finc-logo_title')]) ?>
+        <?= $this->externalLink("https://vufind.org", '<img src="' . $this->imageLink('vufind_logo.png') . '" alt="' . $this->translate('vufind-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('vufind-logo_title')], true) ?>
+        <?= $this->externalLink("https://finc.info", '<img src="' . $this->imageLink('finc_logo.png') . '" alt="' . $this->translate('finc-logo_alt') . '" aria-hidden="true"/>', ['title' => $this->translate('finc-logo_title')], true) ?>
       </div>
     </div>
 </footer>
diff --git a/themes/finc/templates/header.phtml b/themes/finc/templates/header.phtml
index 50d239b701eb65722cd1115f6bdc0c405fae1650..67b8f3acd01568715ac098fdb5c6741fe52f82d9 100644
--- a/themes/finc/templates/header.phtml
+++ b/themes/finc/templates/header.phtml
@@ -109,7 +109,7 @@
                   <?php foreach ($this->layout()->allLangs as $langCode => $langName): ?>
                     <?php if ($langCode !== $this->layout()->userLang) : ?>
                       <li>
-                        <button type="submit" class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()">
+                        <button type="submit" class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" data-href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()">
                           <span class="visible-sm-md-only"><?=$langCode?></span>
                           <span class="hidden-sm-md"><?=$this->displayLanguageOption($langName)?></span>
                         </button>
@@ -127,8 +127,8 @@
   <?php /* finc searchbox: we use searchbox here so it becomes part of the sticky header,
         we need to place this after the navbar-right for anything but mobile  - see flex-container in SCSS:*/ ?>
   <?php if ($this->layout()->searchbox !== false): ?>
-    <div class="search container">
-      <nav class="nav searchbox hidden-print" role="search">
+    <div class="search container" role="search">
+      <nav class="nav searchbox hidden-print">
         <?=$this->layout()->searchbox?>
       </nav>
     </div>
diff --git a/themes/finc/templates/layout/layout.phtml b/themes/finc/templates/layout/layout.phtml
index a269a91e3a942567214f3c2f97c6b620544988bb..8f4a34ab4c74a4cf5262f19e35e5843b65ff16ab 100644
--- a/themes/finc/templates/layout/layout.phtml
+++ b/themes/finc/templates/layout/layout.phtml
@@ -257,7 +257,7 @@ if (!isset($this->layout()->searchbox)) {
 
 <!-- MODAL IN CASE WE NEED ONE -->
 <?php /* move X button to logical pos. in structure + make accessible via tab - CK */ ?>
-<div id="modal" class="modal fade hidden-print" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-hidden="true" aria-describedby="modal-description">
+<div id="modal" class="modal fade hidden-print" tabindex="-1" role="dialog" aria-modal="true" aria-hidden="true" aria-describedby="modal-description">
   <div class="modal-dialog">
     <div class="modal-content">
       <button type="button" class="close" data-dismiss="modal" tabindex="0" aria-label="<?= $this->transEsc('CloseModal') ?>">
diff --git a/themes/finc/templates/myresearch/account.phtml b/themes/finc/templates/myresearch/account.phtml
index b1055a8c55baea6e4f76428bbad1ef93d11cedae..f4e40c2f045ce098ba788dd2cc29355671bfa88a 100644
--- a/themes/finc/templates/myresearch/account.phtml
+++ b/themes/finc/templates/myresearch/account.phtml
@@ -11,7 +11,6 @@
 <?=$this->flashmessages()?>
 
 <form method="post" name="accountForm" id="accountForm" class="form-user-create" data-toggle="validator" role="form">
-  <legend class="sr-only"><?=$this->transEsc('form-legend')?></legend>
   <?=$this->auth()->getCreateFields()?>
   <?=$this->recaptcha()->html($this->useRecaptcha) ?>
   <div class="form-group">
diff --git a/themes/finc/templates/myresearch/mylist.phtml b/themes/finc/templates/myresearch/mylist.phtml
index 6354ae6aa1c7962981d456fc50ba0388082b6014..ed7bf00413d20dec9fd6795100a4b9f99b4e0d0c 100644
--- a/themes/finc/templates/myresearch/mylist.phtml
+++ b/themes/finc/templates/myresearch/mylist.phtml
@@ -57,7 +57,7 @@ $user = $this->auth()->isLoggedIn();
             </a>
             <ul class="dropdown-menu">
               <li><a href="<?=$this->url('myresearch-deletelist')?>?listID=<?=urlencode($list->id)?>&amp;confirm=1"><?=$this->transEsc('confirm_dialog_yes')?></a></li>
-              <li><a href="#"><?=$this->transEsc('confirm_dialog_no')?></a></li>
+              <li><a href="#" class="dropdown-abort"><?=$this->transEsc('confirm_dialog_no')?></a></li>
             </ul>
           </div>
         <?php endif; ?>
@@ -95,5 +95,4 @@ $user = $this->auth()->isLoggedIn();
     <br/><?=$this->recommend($current)?>
   <?php endforeach; ?>
 </div>
-
-<!-- finc: myresearch - mylist - END -->
\ No newline at end of file
+<!-- finc: myresearch - mylist - END -->
diff --git a/themes/finc/templates/myresearch/newpassword.phtml b/themes/finc/templates/myresearch/newpassword.phtml
index b2102e7315a77aac2af970fed31039ebc427488a..e493278a4bcba753d7aed4bbb79edfc44bffe16a 100644
--- a/themes/finc/templates/myresearch/newpassword.phtml
+++ b/themes/finc/templates/myresearch/newpassword.phtml
@@ -23,7 +23,6 @@
   <div class="error"><?=$this->transEsc('recovery_user_not_found') ?></div>
 <?php else: ?>
   <form id="newpassword" class="form-new-password" action="<?=$this->url('myresearch-newpassword') ?>" method="post" data-toggle="validator" role="form">
-    <legend class="sr-only"><?=$this->transEsc('form-legend')?></legend>
     <input type="hidden" value="<?=$this->escapeHtmlAttr($this->auth()->getManager()->getCsrfHash())?>" name="csrf"/>
     <input type="hidden" value="<?=$this->escapeHtmlAttr($this->hash) ?>" name="hash"/>
     <input type="hidden" value="<?=$this->escapeHtmlAttr($this->username) ?>" name="username"/>
diff --git a/themes/finc/templates/record/cart-buttons.phtml b/themes/finc/templates/record/cart-buttons.phtml
index edb7d83ea32988fbf3970619cf617a6174abcc5b..beaa7cbe11028a7347ce015293c8cd923e53f0b9 100644
--- a/themes/finc/templates/record/cart-buttons.phtml
+++ b/themes/finc/templates/record/cart-buttons.phtml
@@ -3,7 +3,7 @@
 <?php if ($cart->isActive()): ?>
 
     <?php $cartId = $this->source . '|' . $this->id; ?>
-    <span class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>">
+    <div class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>">
       <?php /* Make add-to/remove-from bookbag accessible for keyboard navigation - CK */ ?>
     <a class="cart-add hidden<?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0">
       <i class="cart-link-icon fa fa-plus" aria-hidden="true"></i><span class="cart-link-label"><?=$this->transEsc('Add to Book Bag')?></span>
@@ -11,16 +11,17 @@
     <a class="cart-remove hidden<?php if ($cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0">
       <i class="cart-link-icon fa fa-minus-circle" aria-hidden="true"></i> <span class="cart-link-label"><?=$this->transEsc('Remove from Book Bag')?></span>
     </a>
-    <noscript>
-      <form method="post" name="addForm" action="<?=$this->url('cart-processor')?>">
+    <form class="cartProcessorNoJs" method="post" name="addForm" action="<?=$this->url('cart-processor')?>">
+      <noscript>
         <input type="hidden" name="ids[]" value="<?=$this->escapeHtmlAttr($cartId)?>"/>
         <?php if ($cart->contains($cartId)): ?>
           <input class="btn btn-default" type="submit" name="delete" value="<?=$this->transEsc('Remove from Book Bag')?>"/>
         <?php else: ?>
           <input class="btn btn-default" type="submit" name="add" value="<?=$this->transEsc('Add to Book Bag')?>"/>
         <?php endif; ?>
-      </form>
-    </noscript>
-  </span>
+      </noscript>
+    </form>
+    <script>$(document).ready(function() { $(".cartProcessorNoJs").css('display', 'none'); });</script>
+  </div>
 <?php endif; ?>
 <!-- finc: record - cart-buttons END -->
diff --git a/themes/finc/templates/record/pdaform.phtml b/themes/finc/templates/record/pdaform.phtml
index a7faa1f04c5d4fdb60d1075833acab13f2d9eb4f..ab7cb4438655e44e234fa26104d70738ca39a50a 100644
--- a/themes/finc/templates/record/pdaform.phtml
+++ b/themes/finc/templates/record/pdaform.phtml
@@ -36,8 +36,14 @@ $this->layout()->breadcrumbs = '<li>' . $this->searchMemory()->getLastSearchLink
       <?=$this->recaptcha()->html($this->useRecaptcha)?>
     </span>
     <input type="submit" class="btn btn-primary" role="button" name="submit" value="<?=$this->transEsc('Submit')?>"/>
-    <a class="btn btn-primary" data-lightbox-ignore data-toggle="tooltip" title="<?=$this->transEsc('PDA::pda_open_new_window')?>" href="<?=$this->interlibraryloan()->getSwbLink($this->driver)?>" target="_blank"><?=$this->transEsc('PDA::pda_tab_interlibrary_button')?>
-    </a>
+    <?= $this->externalLink(
+          $this->interlibraryloan()->getSwbLink($this->driver),
+          $this->translate('PDA::pda_tab_interlibrary_button'),
+          ['class' => 'btn btn-primary',
+           'data-lightbox-ignore' => '',
+           'data-toggle' => 'tooltip',
+           'title' => $this->translate('PDA::pda_open_new_window')]
+         ) ?>
     <button class="btn btn-transparent" type="button" data-dismiss="modal" href="#"><?=$this->transEsc('Reset')?></button>
   </div>
 
diff --git a/themes/finc/templates/record/view.phtml b/themes/finc/templates/record/view.phtml
index 1f97d387fad4ff410d80c391850a4220a5bfe5a1..3749c2121c6fc81e1f807f1b38c5e40014cb3a48 100644
--- a/themes/finc/templates/record/view.phtml
+++ b/themes/finc/templates/record/view.phtml
@@ -40,15 +40,17 @@
       <?= $this->record($this->driver)->getCoreMetadata() ?>
       
       <?php if (count($this->tabs) > 0): ?>
-        <a name="tabnav"></a>
+        <?php /* swap deprecated 'name' for 'ID' - CK */ ?>
+        <a id="tabnav"></a>
         <div class="record-tabs">
+          <?php /* DO NOT add 'role=tablist' for accessibility, see #19938 - CK */ ?>
           <ul class="nav nav-tabs">
               <?php foreach ($this->tabs as $tab => $obj): ?>
                   <?php // add current tab to breadcrumbs if applicable:
                   $desc = $obj->getDescription();
                   $tabName = preg_replace("/\W/", "-", strtolower($tab));
                   $tabClasses = ['record-tab', $tabName];
-                  if (0 === strcasecmp($this->activeTab, $tab)) {
+                  if (($isActiveTab = 0 === strcasecmp($this->activeTab, $tab))) {
                       if (!$this->loadInitialTabWithAjax || !$obj->supportsAjax()) {
                           $tabClasses[] = 'active';
                       }
@@ -63,16 +65,27 @@
                       $tabClasses[] = 'noajax';
                   }
                   ?>
+                <?php /* DO NOT add role="tab" BUT DO ADD aria-controls and ID for accessibility --
+                      'aria-selected' (true/false) needs to be set via record.js - CK */ ?>
                 <li class="<?= implode(' ', $tabClasses) ?>" data-tab="<?= $tabName ?>">
-                  <a
-                    href="<?= $this->recordLink()->getTabUrl($this->driver, $tab) ?>#tabnav"<?php if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)): ?> data-background<?php endif ?>><?= $this->transEsc($desc) ?></a>
+                  <a href="<?= $this->recordLink()->getTabUrl($this->driver, $tab) ?>#tabnav"
+                    <?php if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)): ?> data-background<?php endif ?>
+                     aria-selected="<?= $isActiveTab ? "true" : "false" ?>"
+                     aria-controls="<?= $tabName ?>"
+                     id="<?= $tabName ?>-tabselector">
+                    <?= $this->transEsc($desc) ?>
+                    <span class="sr-only load-tab-content"><?= $this->transEsc('load_tab_content_hint') ?></span></a>
                 </li>
               <?php endforeach; ?>
           </ul>
 
-          <div class="tab-content">
+          <div class="tab-content" aria-live="polite" tabindex="-1">
               <?php if (!$this->loadInitialTabWithAjax || !isset($activeTabObj) || !$activeTabObj->supportsAjax()): ?>
-                <div class="tab-pane active <?= $this->escapeHtmlAttr($this->activeTab) ?>-tab">
+                <?php /* Add ID, role and aria-labelledby for accessibility - CK */ ?>
+                <div class="tab-pane active <?= $this->escapeHtmlAttr($this->activeTab) ?>-tab"
+                     role="tabpanel"
+                     id="<?= $this->escapeHtmlAttr($this->activeTab) ?>"
+                     aria-labelledby="<?= $this->activeTab ?>-tabselector">
                     <?= isset($activeTabObj) ? $this->record($this->driver)->getTab($activeTabObj) : '' ?>
                 </div>
               <?php endif; ?>
diff --git a/themes/finc/templates/search/results.phtml b/themes/finc/templates/search/results.phtml
index de9ade6fd08919a365af15829592376107b95f24..8bac2e91e997dee739e6556a1349061de5c306b5 100644
--- a/themes/finc/templates/search/results.phtml
+++ b/themes/finc/templates/search/results.phtml
@@ -85,15 +85,15 @@ $this->headScript()->appendFile("check_save_statuses.js");
     <?php if ($recordTotal > 0): ?>
     <?php /* finc: use spans for easier to show/hide choices - CK */ ?>
       <div class="search-controls">
-        <span class="limit">
+        <div class="limit">
         <?=$this->render('search/controls/limit.phtml')?>
-        </span>
-        <span class="sort right">
+        </div>
+        <div class="sort right">
         <?=$this->render('search/controls/sort.phtml')?>
-        </span>
-        <span class="view">
+        </div>
+        <div class="view">
         <?=$this->render('search/controls/view.phtml')?>
-        </span>
+        </div>
       </div>
     <?php endif; ?>
   </div>