diff --git a/local/languages/de.ini b/local/languages/de.ini
index abea7f9dc4bd7343ef26c63abe8ab4fd8970a3be..6428ea9568274ecf180264ac9f35778dece09cc1 100644
--- a/local/languages/de.ini
+++ b/local/languages/de.ini
@@ -2078,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 ecafdfcc9672f43685fdb55e0c1084f4500d76d4..f1aa7b7cf478120dce8ec464464eec64899cca2b 100644
--- a/local/languages/en.ini
+++ b/local/languages/en.ini
@@ -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"
@@ -2166,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/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-en.min.js b/themes/finc-accessibility/js/vendor/bootstrap-accessibility-en.min.js
index 630f112aed7e6a0149687a95215c623908bf0404..fb3ad97f9a4462ee6e30273db5a316df5ee84feb 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) {
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/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; ?>