Completed
Last Updated: 04 Dec 2025 12:51 by ADMIN
David
Created on: 01 Dec 2025 11:41
Category: UI for ASP.NET AJAX
Type: Bug Report
0
RadGrid ADA "Ensure elements that have scrollable content are accessible by keyboard"

 

Attached my grid code. Most columns are removed for readability

    <telerik:RadGrid ID="grdChanges" runat="server" Width="1140" 
        skin="WebBlue" style="margin-top:13px; margin-right:13px; outline: 0 !important;"
        ShowFooter="false" AllowSorting="false">      
    <ClientSettings>
            <Scrolling AllowScroll="True" ScrollHeight="487px" UseStaticHeaders="true" />
    </ClientSettings>                      
    <MasterTableView GroupLoadMode="Client" AutoGenerateColumns="False" HeaderStyle-Font-Bold="true"> 
        <HeaderStyle CssClass="InnerHeaderStyle"/>
        <ItemStyle CssClass="InnerItemStyle"/>
        <AlternatingItemStyle CssClass="InnerAlernatingItemStyle"/>
        <CommandItemStyle CssClass="CommandHeaderStyle" />

        <ColumnGroups>
           <telerik:GridColumnGroup Name="Passenger Trips" HeaderText="Passenger Trips" HeaderStyle-HorizontalAlign="Center"/>           
           <telerik:GridColumnGroup Name="Ton Trips" HeaderText="Ton Trips" HeaderStyle-HorizontalAlign="Center"/>                                                         
           <telerik:GridColumnGroup Name="Miles Per Trip" HeaderText="Miles Per Trip" HeaderStyle-HorizontalAlign="Center"/>           
           <telerik:GridColumnGroup Name="Miles Per Hour" HeaderText="Miles Per Hour" HeaderStyle-HorizontalAlign="Center"/>                          
        </ColumnGroups> 

        <Columns> 

            <telerik:GridNumericColumn DataField="MilesPerHour_Proj" HeaderText="Project"  
                                       ColumnGroupName ="Miles Per Hour"
                                       DataFormatString="{0:N1}" DecimalDigits="0"
                                       HeaderStyle-HorizontalAlign="Center" 
                                       HeaderStyle-Width="60px" ItemStyle-BackColor="White"
                                       ItemStyle-HorizontalAlign="Right" AllowRounding="true" />

            <telerik:GridNumericColumn DataField="MilesPerHour_Base" HeaderText="Base"  
                                       ColumnGroupName ="Miles Per Hour"
                                       DataFormatString="{0:N1}" DecimalDigits="0"
                                       HeaderStyle-HorizontalAlign="Center" 
                                       HeaderStyle-Width="60px" ItemStyle-BackColor="White"
                                       ItemStyle-HorizontalAlign="Right" AllowRounding="true" />                            

            <telerik:GridNumericColumn DataField="MilesPerHourChange" HeaderText="Change"  
                                       ColumnGroupName ="Miles Per Hour"
                                       DataFormatString="{0:N1}" DecimalDigits="0"
                                       HeaderStyle-HorizontalAlign="Center" 
                                       HeaderStyle-Width="60px" ItemStyle-BackColor="White"
                                       ItemStyle-HorizontalAlign="Right" AllowRounding="true" />
        </Columns> 

        <NoRecordsTemplate> 
            <div style="padding: 5px"> 
                No records available. 
            </div> 
        </NoRecordsTemplate> 

    </MasterTableView> 
    <FilterMenu EnableTheming="True"> 
        <CollapseAnimation Duration="200" Type="OutQuint" /> 
    </FilterMenu> 
</telerik:RadGrid> 

                
1 comment
ADMIN
Vasko
Posted on: 02 Dec 2025 09:25

Hello David,

Thank you for reporting this issue.

When Scrolling is enabled, the Grid generates a wrapper container around its table element to enable the scrolling functionality (as table elements by default are not scrollable).

The ADA error is related to the wrapper element (div with class "rgDataDiv"), as it is considered a "scrollable" element, therefore it needs to be accessible via keyboard input. To do that, you can override the internal function for aria support and add a TabIndex to it (the highlighted part in green):

let $T = Telerik.Web.UI;

if ($T && $T.RadGrid) {
    $T.RadGrid.prototype._initializeAriaSupport = function() {
        var wrapper = this.get_element();
        var isLightweight = this._renderMode === Telerik.Web.UI.RenderMode.Lite;
        var clientSettings = this.ClientSettings;

        var i;
        var j;
        var length;
        var row;
        var cell;
        var headers;
        var atleastOneEditRow = false;

        var initHeader = function (header) {
            header.setAttribute('role', 'columnheader');

            if (!header.parentNode.getAttribute('role')) {
                header.parentNode.setAttribute('role', 'row');
            }

            var sortLink = header.getElementsByTagName('a')[0];

            if (sortLink) {
                var sortInput = header.getElementsByTagName('input')[0];

                if (sortInput) {
                    if (sortInput.className.indexOf('rgSortAsc') > -1) {
                        header.setAttribute('aria-sort', 'ascending');
                    } else if (sortInput.className.indexOf('rgSortDesc') > -1) {
                        header.setAttribute('aria-sort', 'descending');
                    }
                } else {
                    header.setAttribute('aria-sort', 'none');
                }
            }
        };

        if (wrapper.querySelectorAll) {
            headers = wrapper.querySelectorAll('th.rgHeader');

            for (i = 0, length = headers.length; i < length; i++) {
                initHeader(headers[i]);
            }
        } else {
            headers = wrapper.getElementsByTagName('th');

            for (i = 0, length = headers.length; i < length; i++) {
                var header = headers[i];

                if (header.className.indexOf('rgHeader') > -1) {
                    initHeader(header);
                }
            }
        }

        var cells = wrapper.getElementsByTagName('td');

        for (i = 0, length = cells.length; i < length; i++) {
            cell = cells[i];
            row = cell.parentNode;
            var rowClass = row.className;

            if (cell.className.indexOf('rgExpandCol') > -1 || cell.className.indexOf('rgGroupCol') > -1) {
                cell.setAttribute('role', 'gridcell');
                row.setAttribute('role', 'row');

                if (rowClass.indexOf('rgGroupHeader') > -1 || row.id) {
                    // mark expanded states of group and parent items
                    var expButton = cell.getElementsByTagName(isLightweight ? 'button' : 'input')[0];

                    if (expButton) {
                        if (!isLightweight) {
                            expButton.setAttribute('role', 'button');
                        }

                        if (expButton.className.indexOf('rgCollapse') > -1) {
                            cell.setAttribute('aria-expanded', 'true');
                            expButton.setAttribute('aria-expanded', 'true');
                        } else if (expButton.className.indexOf('rgExpand') > -1) {
                            cell.setAttribute('aria-expanded', 'false');
                            expButton.setAttribute('aria-expanded', 'false');
                        }
                    }

                    // mark group headers
                    if (rowClass.indexOf('rgGroupHeader') > -1 && !row.getAttribute('role')) {
                        row.setAttribute('role', 'row');
                    }
                }
            } else if (rowClass.indexOf('rgRow') > -1 || rowClass.indexOf('rgAltRow') > -1 || rowClass.indexOf('rgFooter') > -1) {
                cell.setAttribute('role', 'gridcell');

                // mark data rows
                if (!row.getAttribute('role')) {
                    row.setAttribute('role', 'row');

                    if (rowClass.indexOf('rgSelectedRow') > -1) {
                        row.setAttribute('aria-selected', 'true');
                        row.tabIndex = 1;
                    }
                }
            } else if (cell.className.indexOf('rgCommandCell') > -1 && !row.getAttribute('role')) {
                // mark command item
                cell.setAttribute('role', 'presentation');
                row.setAttribute('role', 'presentation');
            } else if (row.className.indexOf('rgPager') > -1 && !row.getAttribute('role')) {
                // mark pager
                row.setAttribute('role', 'presentation');
                var divs = row.getElementsByTagName('div');
                var pagerBtn = row.querySelector('.rgPager .RadComboBox .rcbInner button');

                if (pagerBtn) {
                    var pagerComboInput = $telerik.findElement(pagerBtn.parentNode, 'PageSizeComboBox_Input');

                    pagerBtn.value = pagerComboInput.title;
                    pagerComboInput.setAttribute('aria-label', pagerComboInput.title);
                }

                for (var di = 0; di < divs.length; di++) {
                    var div = divs[di];

                    if (div.className && div.className.indexOf('rgWrap') > -1) {
                        var inputs = div.getElementsByTagName('input');

                        for (j = 0; j < inputs.length; j++) {
                            var inp = inputs[j];
                            var className = inp.className;

                            if (inp.type == 'submit' && className
                                && (className.indexOf('PagePrev') > -1
                                    || className.indexOf('PageFirst') > -1
                                    || className.indexOf('PageNext') > -1
                                    || className.indexOf('PageLast') > -1
                                    || className.indexOf('PagerButton') > -1)) {
                                inp.setAttribute('role', 'button');
                            }
                        }
                    }
                }
            } else if (row.className.indexOf('rgEditRow') > -1 && !row.getAttribute('role')) {
                // mark edit row
                row.setAttribute('role', 'row');
                row.removeAttribute('aria-readonly');
                atleastOneEditRow = true;
            } else if (!row.id) {
                // mark rows with no IDs in grid tables
                var table = row.parentNode.tagName.toLowerCase() === 'table' ? row.parentNode : row.parentNode.parentNode;

                if (table && table.className.indexOf('rgMasterTable') > -1 || table.className.indexOf('rgDetailTable') > -1) {
                    if (!row.getAttribute('role')) {
                        row.setAttribute('role', 'presentation');
                    }
                }
            }
        }

        if (this._groupPanel) {
            this._groupPanel.get_element().setAttribute('role', 'presentation');
        }

        //static headers
        var $dataTables;
        var $masterTableDataDiv;
        if (this.ClientSettings.Scrolling && this.ClientSettings.Scrolling.UseStaticHeaders) {
            $dataTables = $telerik.$(wrapper).find('.rgDataDiv>table.rgMasterTable, table.rgDetailTable');

            let $dataDivWrapper = $telerik.$(wrapper).find(".rgDataDiv");
            $dataDivWrapper.attr("tabIndex", 0);

            var $layoutTableElements = $telerik.$(wrapper).find('table[id$=Pager], table[id$=Header],table[id$=Footer]');
            $layoutTableElements.removeAttr('summary');
            //$layoutTableElements.find('caption').remove();
            $layoutTableElements.find('th:empty').remove();
            $layoutTableElements.find('thead:empty').remove();
            $layoutTableElements.attr('role', 'presentation');

            $masterTableDataDiv = $telerik.$(wrapper).find('.rgDataDiv>table.rgMasterTable');

            var mainDataTableTbodyId = "";
            var headerTableTHeadId = "";
            var footerTableTbodyId = "";

            var $mainDataTableTbody = $masterTableDataDiv.find('tbody');
            if ($mainDataTableTbody.length > 0) {
                mainDataTableTbodyId = $mainDataTableTbody.parent()[0].id + '_tbody';
                $mainDataTableTbody.attr('id', mainDataTableTbodyId);
            }

            var $headerTableThead = $telerik.$(wrapper).find('table[id$=Header]>thead');
            if ($headerTableThead.length > 0) {
                headerTableTHeadId = $headerTableThead.parent()[0].id + '_thead';
                $headerTableThead.attr('id', headerTableTHeadId);
            }

            var $footerTableTbody = $telerik.$(wrapper).find('table[id$=Footer]>tbody');
            if ($footerTableTbody.length > 0) {
                footerTableTbodyId = $footerTableTbody.parent()[0].id + '_tbody';
                $footerTableTbody.attr('id', footerTableTbodyId);
            }

            $masterTableDataDiv.attr('aria-owns', headerTableTHeadId + ' ' + mainDataTableTbodyId + ' ' + footerTableTbodyId);

            var $topPagerTable = $telerik.$(wrapper).find('table[id$=TopPager]');
            $topPagerTable.attr('role', 'toolbar');
        }
        else {
            $dataTables = $telerik.$(wrapper).find('table.rgMasterTable, table.rgDetailTable');
            $masterTableDataDiv = $telerik.$(wrapper).find('table.rgMasterTable');
        }


        var that = this;
        $dataTables.each(function () {
            this.setAttribute('role', 'grid');
            if (clientSettings.Selecting.AllowRowSelect && that.get_allowMultiRowSelection()) {
                this.setAttribute('aria-multiselectable', 'true');
            }
        });

        if (this._detailTables.length) {
            $masterTableDataDiv.attr('role', 'treegrid');
        }


        //headercontextfilter
        if (this._getHeaderContextMenu()) {
            var $headerContextMenuButtons = $telerik.$(wrapper).find('.rgOptions');
            $headerContextMenuButtons.each(function () {
                this.setAttribute('aria-label', 'Column Menu');
            })
            var contextMenu = this._getHeaderContextMenu();

            var filterMenuItem = contextMenu.findItemByValue("FilterMenuParent");
            if (filterMenuItem && filterMenuItem.get_element()) {
                var $filterMenuElement = $telerik.$(filterMenuItem.get_element());
                //filter function comboboxes 
                $filterMenuElement.find('input[id$="FirstCond_Input"]').attr('aria-label', 'First Condition Filter Function');
                $filterMenuElement.find('input[id$="SecondCond_Input"]').attr('aria-label', 'Second Condition Filter Function');
                //filter value inputs - textbox, numerictextbox, datepickers
                $filterMenuElement.find('input[id$="FirstCond"], input[id$="FirstCond_dateInput"]').attr('aria-label', 'First Condition Filter Value');
                $filterMenuElement.find('input[id$="SecondCond"], input[id$="SecondCond_dateInput"]').attr('aria-label', 'Second Condition Filter Value');
            }

            var filterListItem = contextMenu.findItemByValue("FilterList");
            if (filterListItem && filterListItem.get_element()) {
                var $filterListElement = $telerik.$(filterListItem.get_element());
                $filterListElement.find('input[id$="filterCheckListSearch"]').attr('aria-label', 'Filter Checklist Search');
            }
        }
    }
}

This fix will be available in the next official release, until then please use the above override.

Regards,
Vasko
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources