f9fa02315060e80003f55f78c770dea0830d128c
lrnassar
  Tue Apr 7 15:33:47 2026 -0700
Make Recent Genomes and Connected Hubs cards keyboard-accessible on hgGateway. refs #37342

diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js
index 85240aa9215..ee2980cf0b6 100644
--- src/hg/js/hgGateway.js
+++ src/hg/js/hgGateway.js
@@ -1841,60 +1841,73 @@
         // emptyMessage: message to show when no items
         // onCardClick: optional callback for card clicks (defaults to setDbFromAutocomplete)
         $panel.empty();
 
         if (!items || items.length === 0) {
             if (emptyMessage) {
                 $panel.html('<div class="recentGenomesEmpty">' + emptyMessage + '</div>');
             }
             return;
         }
 
         var clickHandler = onCardClick || setDbFromAutocomplete;
 
         // Render each item as a card (vertical layout)
         items.forEach(function(item) {
-            var $card = $('<div class="recentGenomeCard"></div>');
+            var $card = $('<div class="recentGenomeCard" tabindex="0" role="button"></div>');
             var label = item.label || item.shortLabel || item.value || item.genome || item.commonName;
             var genome = trackHubSkipHubName(item.genome || item.db || '');
 
+            // Build aria-label from label and genome
+            var ariaLabel = label;
+            if (genome && label.indexOf(genome) < 0) {
+                ariaLabel = label + ' (' + genome + ')';
+            }
+            $card.attr('aria-label', ariaLabel);
+
             $card.append('<div class="recentGenomeLabel">' + escapeHtml(label) + '</div>');
             if (genome && label.indexOf(genome) < 0) {
                 $card.append('<div class="recentGenomeDb">' + escapeHtml(genome) + '</div>');
             }
 
             // Add category label: "External" for assembly hubs, "UCSC Curated" for everything else.
             // The indexOf check handles both the new "Assembly Hub" category from handleSetDb
             // and legacy localStorage entries from addHubsToList or older code.
             var shortCategory;
             var catForDisplay = item.originalCategory || item.category;
             if (catForDisplay && catForDisplay.indexOf('Assembly Hub') >= 0) {
                 shortCategory = 'External';
             } else {
                 shortCategory = 'UCSC Curated';
             }
             $card.append('<div class="recentGenomeCategory">' + escapeHtml(shortCategory) + '</div>');
 
-            // Store item data for click handler
+            // Store item data for click/keyboard handler
             $card.data('item', item);
             $card.on('click', function() {
                 var clickedItem = $(this).data('item');
                 clickHandler(clickedItem);
                 // Highlight selected card in this panel
                 $panel.find('.recentGenomeCard').removeClass('selected');
                 $(this).addClass('selected');
             });
+            $card.on('keydown', function(e) {
+                if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    $(this).trigger('click');
+                }
+            });
 
             $panel.append($card);
         });
     }
 
     function displayRecentGenomesInPanel() {
         // Display recent genomes in the panel on page load and after genome selection
         var recentGenomes = getRecentGenomes();
         // Show the section (hidden by default in HTML)
         $('#recentGenomesTitle').show();
         $('#recentGenomesSection').show();
         var emptyMessage = 'Search for a genome above, or click a popular species icon';
         renderGenomeCards(recentGenomes, $('#recentGenomesList'), emptyMessage);
         // Clear any selected state from connected hub cards (selection was transient,
         // the genome is now reflected in the recent genomes panel)