651f959d7553ee0256b1e0b37ffa83d64709511d
lrnassar
  Mon Mar 16 17:57:16 2026 -0700
Adding accessible labels to form controls across main CGI pages. Extends cheapcgi and hui libraries with aria-label support for track visibility dropdowns, and adds <label> elements to hgBlat, hgTables, hgPcr, and hgGateway form controls. Also adds Form Control Labels section to accessibility page. refs #37253

diff --git src/hg/hgTables/mainPage.c src/hg/hgTables/mainPage.c
index f7140264f1a..fa9488268e2 100644
--- src/hg/hgTables/mainPage.c
+++ src/hg/hgTables/mainPage.c
@@ -65,31 +65,31 @@
 }
 
 void makeRegionButton(char *val, char *selVal)
 /* Make region radio button including a little Javascript
  * to save selection state. */
 {
 makeRegionButtonExtraHtml(val, selVal, NULL);
 }
 
 struct grp *showGroupField(char *groupVar, char *event, char *groupScript,
     struct sqlConnection *conn, boolean allTablesOk)
 /* Show group control. Returns selected group. */
 {
 struct grp *group, *groupList = fullGroupList;
 struct grp *selGroup = findSelectedGroup(groupList, groupVar);
-hPrintf("<B>Group:</B>\n");
+hPrintf("<label for='%s'><B>Group:</B></label>\n", groupVar);
 hPrintf("<SELECT NAME=%s id='%s'>\n", groupVar, groupVar);
 jsOnEventById(event,groupVar,groupScript);
 for (group = groupList; group != NULL; group = group->next)
     {
     if (allTablesOk || differentString(group->name, "allTables"))
         hPrintf(" <OPTION VALUE=%s%s>%s</OPTION>\n", group->name,
                 (group == selGroup ? " SELECTED" : ""),
                 group->label);
     }
 hPrintf("</SELECT>\n");
 return selGroup;
 }
 
 static void addIfExists(struct hash *hash, struct slName **pList, char *name)
 /* Add name to tail of list if it exists in hash. */
@@ -123,45 +123,45 @@
 slFreeList(&dbList);
 return selDb;
 }
 
 struct trackDb *showTrackField(struct grp *selGroup, char *trackVar, char *event, char *trackScript,
                                boolean disableNoGenome)
 /* Show track control. Returns selected track. */
 {
 struct trackDb *track, *selTrack = NULL;
 if (trackScript == NULL)
     trackScript = "";
 if (sameString(selGroup->name, "allTables"))
     {
     char *selDb = findSelDb();
     struct slName *dbList = getDbListForGenome(), *db;
-    hPrintf("<B>database:</B>\n");
+    hPrintf("<label for='%s'><B>database:</B></label>\n", trackVar);
     hPrintf("<SELECT NAME=\"%s\" id='%s'>\n", trackVar, trackVar);
     jsOnEventById(event, trackVar, trackScript);
     for (db = dbList; db != NULL; db = db->next)
 	{
 	hPrintf(" <OPTION VALUE=%s%s>%s</OPTION>\n", db->name,
 		(sameString(db->name, selDb) ? " SELECTED" : ""),
 		db->name);
 	}
     hPrintf("</SELECT>\n");
     }
 else
     {
     boolean allTracks = sameString(selGroup->name, "allTracks");
-    hPrintf("<B>Track:</B>\n");
+    hPrintf("<label for='%s'><B>Track:</B></label>\n", trackVar);
     hPrintf("<SELECT NAME=\"%s\" id='%s'>\n", trackVar, trackVar);
     jsOnEventById(event, trackVar, trackScript);
     if (allTracks)
         {
 	selTrack = findSelectedTrack(fullTrackList, NULL, trackVar);
 	slSort(&fullTrackList, trackDbCmpShortLabel);
 	}
     else
 	{
 	selTrack = findSelectedTrack(fullTrackList, selGroup, trackVar);
 	}
     boolean selTrackIsDisabled = FALSE;
     struct trackDb *firstEnabled = NULL;
     for (track = fullTrackList; track != NULL; track = track->next)
 	{
@@ -252,31 +252,31 @@
 struct slName *name, *nameList = NULL;
 char *selTable;
 
 if (track == NULL)
     nameList = tablesForDb(findSelDb());
 else
     nameList = cartTrackDbTablesForTrack(database, track, useJoiner);
 
 /* Get currently selected table.  If it isn't in our list
  * then revert to first in list. */
 selTable = cartUsualString(cart, varName, nameList->name);
 if (!slNameInListUseCase(nameList, selTable))
     selTable = nameList->name;
 
 /* Print out label and drop-down list. */
-hPrintf("<B>Table: </B>");
+hPrintf("<label for='%s'><B>Table: </B></label>", varName);
 hPrintf("<SELECT NAME=\"%s\" id='%s'>\n", varName, varName);
 jsOnEventById("change", varName, onChangeTable());
 struct trackDb *selTdb = NULL;
 for (name = nameList; name != NULL; name = name->next)
     {
     struct trackDb *tdb = NULL;
     if (curTrack != NULL && isHubTrack(curTrack->track))
         {
         if (sameString(curTrack->track, name->name))
             tdb = curTrack;
         else if (curTrack->subtracks != NULL)
             {
             // maybe a subtrack is what we're looking for
             struct trackDb *sub = subTdbFind(curTrack, name->name);
             if (sub)
@@ -482,31 +482,31 @@
 struct outputType otWigData =     { NULL, outWigData,     "Data points", };
 struct outputType otWigBed =      { NULL, outWigBed,      "Bed format", };
 struct outputType otMaf =         { NULL, outMaf,         "MAF - multiple alignment format", };
 struct outputType otChromGraphData =      { NULL, outChromGraphData,       "Data points", };
 struct outputType otMicroarrayNames =     { NULL, outMicroarrayNames,     "Microarray names", };
 struct outputType otMicroarrayGroupings = { NULL, outMicroarrayGroupings, "Microarray groupings", };
 
 static void showOutputTypeRow(boolean isWig, boolean isBedGr,
     boolean isPositional, boolean isMaf, boolean isChromGraphCt,
     boolean isPal, boolean isMicroarray, boolean isHalSnake)
 /* Print output line. */
 {
 struct outputType *otList = NULL, *otDefault = NULL;
 boolean bedifiedOnly = (anySubtrackMerge(database, curTable) || anyIntersection());
 
-hPrintf("<TR><TD><DIV ID=\"output-select\"><B>Output format:</B>\n");
+hPrintf("<TR><TD><DIV ID=\"output-select\"><label for='outputTypeDropdown'><B>Output format:</B></label>\n");
 
 if (isBedGr)
     {
     if (! bedifiedOnly)
 	{
 	slAddTail(&otList, &otAllFields);
 	slAddTail(&otList, &otSelected);
 	}
     slAddTail(&otList, &otWigData);
     slAddTail(&otList, &otWigBed);
     slAddTail(&otList, &otCustomTrack);
     slAddTail(&otList, &otHyperlinks);
     }
 else if (isWig)
     {
@@ -779,49 +779,49 @@
     boolean disableGenome = ((curTrack && cartTrackDbIsNoGenome(database, curTrack->table)) ||
                              (curTable && cartTrackDbIsNoGenome(database, curTable)));
     // If "genome" is selected but not allowed, force it to "range":
     if (sameString(regionType, hgtaRegionTypeGenome) && disableGenome)
         regionType = hgtaRegionTypeRange;
     jsTrackingVar("regionType", regionType);
     if (disableGenome)
         {
         makeRegionButtonExtraHtml(hgtaRegionTypeGenome, regionType, "DISABLED");
         hPrintf("&nbsp;<span"NO_GENOME_CLASS">genome (unavailable for selected track)</span>"
                 "&nbsp;");
         }
     else
         {
         makeRegionButton(hgtaRegionTypeGenome, regionType);
-        hPrintf("&nbsp;Genome&nbsp;");
+        hPrintf("&nbsp;<label for='%s_%s'>Genome</label>&nbsp;", hgtaRegionType, hgtaRegionTypeGenome);
         }
     if (doEncode)
         {
 	makeRegionButton(hgtaRegionTypeEncode, regionType);
-	hPrintf("&nbsp;ENCODE Pilot regions&nbsp;");
+	hPrintf("&nbsp;<label for='%s_%s'>ENCODE Pilot regions</label>&nbsp;", hgtaRegionType, hgtaRegionTypeEncode);
 	}
     makeRegionButton(hgtaRegionTypeRange, regionType);
-    hPrintf("&nbsp;Position&nbsp;");
+    hPrintf("&nbsp;<label for='%s'>Position</label>&nbsp;", hgtaRange);
     hPrintf("<INPUT TYPE=TEXT NAME=\"%s\" id='%s' SIZE=26 VALUE=\"%s\">\n",
     	hgtaRange, hgtaRange, range);
     jsOnEventById("focus", hgtaRange, 
 	jsRadioUpdate(hgtaRegionType, "regionType", "range"));
     cgiMakeButton(hgtaDoLookupPosition, "Lookup");
     hPrintf("&nbsp;");
     if (userRegionsFileName() != NULL)
 	{
 	makeRegionButton(hgtaRegionTypeUserRegions, regionType);
-	hPrintf("&nbsp;Defined regions&nbsp;");
+	hPrintf("&nbsp;<label for='%s_%s'>Defined regions</label>&nbsp;", hgtaRegionType, hgtaRegionTypeUserRegions);
 	cgiMakeButton(hgtaDoSetUserRegions, "Change");
 	hPrintf("&nbsp;");
 	cgiMakeButton(hgtaDoClearUserRegions, "Clear");
 	}
     else
 	cgiMakeButton(hgtaDoSetUserRegions, "Define regions");
     hPrintf("</DIV>");
     hPrintf("</TD></TR>\n");
 
     if (disableGenome) { // no need to check curTrack for NULL, disableGenome can only be set if curTable is set
         hPrintf("<tr><td>");
         printNoGenomeWarning(curTrack);
         hPrintf("</td></tr>");
     }
 
@@ -959,52 +959,56 @@
 
     hPrintf("</TD></TR>\n");
     }
 
 /* Print output type line. */
 
 printStep(stepNumber++);
 showOutputTypeRow(isWig, isBedGr, isPositional, isMaf, isChromGraphCt, isPal, isArray, isHalSnake);
 
 /* Print output destination line. */
     {
     char *compressType = cartUsualString(cart, hgtaCompressType, textOutCompressNone);
     char *fieldSep = cartUsualString(cart, hgtaOutSep, outTab);
     char *fileName = cartUsualString(cart, hgtaOutFileName, "");
     hPrintf("<TR><TD>\n");
-    hPrintf("<DIV ID=\"filename-select\"><B>Output filename:</B>&nbsp;");
+    hPrintf("<DIV ID=\"filename-select\"><label for='%s'><B>Output filename:</B></label>&nbsp;", hgtaOutFileName);
     cgiMakeTextVar(hgtaOutFileName, fileName, 29);
     hPrintf("&nbsp;(<span id='excelOutNote' style='display:none'>add .csv extension if opening in Excel, </span>leave blank to keep output in browser)</DIV></TD></TR>\n");
     hPrintf("<TR><TD>\n");
     hPrintf("<B>Output field separator:&nbsp;</B>");
 
     // tab or csv output
+    hPrintf("<label>");
     cgiMakeRadioButton(hgtaOutSep, outTab, sameWord(outTab, fieldSep));
-    hPrintf("&nbsp;tsv (tab-separated)&nbsp&nbsp;");
+    hPrintf("&nbsp;tsv (tab-separated)</label>&nbsp&nbsp;");
 
+    hPrintf("<label>");
     cgiMakeRadioButton(hgtaOutSep, outCsv, sameWord(outCsv, fieldSep));
-    hPrintf("&nbsp;csv (for excel)&nbsp;");
+    hPrintf("&nbsp;csv (for excel)</label>&nbsp;");
 
     hPrintf("</TD></TR>\n");
     hPrintf("<TR><TD>\n");
     hPrintf("<B>File type returned:&nbsp;</B>");
+    hPrintf("<label>");
     cgiMakeRadioButton(hgtaCompressType, textOutCompressNone,
         sameWord(textOutCompressNone, compressType));
-    hPrintf("&nbsp;Plain text&nbsp;");
+    hPrintf("&nbsp;Plain text</label>&nbsp;");
+    hPrintf("<label>");
     cgiMakeRadioButton(hgtaCompressType, textOutCompressGzip,
         sameWord(textOutCompressGzip, compressType));
-    hPrintf("&nbsp;Gzip compressed");
+    hPrintf("&nbsp;Gzip compressed</label>");
     hPrintf("</TD></TR>\n");
     }
 
 hPrintf("</TABLE>\n");
 
 
 /* Submit buttons. */
     {
     hPrintf("<BR><DIV ID=\"submit-select\">\n");
     if (isWig || isBam || isVcf || isLongTabix || isHic)
 	{
 	char *name;
 	extern char *maxOutMenu[];
 	char *maxOutput = maxOutMenu[0];