Over the last few months, we have been working on improving accessibility for all Wijmo controls, especially the FlexGrid. And we are happy to report that the latest version of the FlexGrid is not only one of the fastest and most full-featured grids on the market, but is also one of the most accessible.
In short, ARIA is a technology that makes advanced Web applications accessible and usable to people with disabilities. It is based on a standard set of attributes added to HTML elements to provide a framework for describing features for user interaction, and how assistive technologies can use this framework.
These attributes extend the stable HTML specification to provide the extensibility and flexibility required by emerging technologies and standards.
When we first released Wijmo 5, back in 2014, the WAI-ARIA 1.0 standard was about to be published, and few assistive tools supported it. Fortunately, that situation has changed. WAI-ARIA is becoming well-supported and better understood.
Coincidentally, we recently started to get lots of requests from important customers with dedicated accessibility teams who were willing to work with us to improve the Wijmo controls and make them accessible.
This article describes the work we did, which went far beyond adding a few ARIA attributes to the controls. Interestingly, the folks at Facebook faced the same challenges when implementing ARIA-compliant grid elements. Their work is described the ARIA Grid: Supporting nonvisual layout and keyboard traversal document, which contains tons of useful, practical information about ARIA and assistive technologies.
ARIA attributes are important because they offer a standard way to provide accessibility tools with information about the role and state of generic HTML elements such as “divs”.
Adding the appropriate ARIA attributes to the grid elements was the first step we took when we decided to improve the grid’s accessibility. As usual, in practice things are a little more complicated than in theory. The ARIA standard dictates the following hierarchy for grid elements:
<div role="grid"> <div role="row"> <div role="gridcell"></div> or <div role="columnheader"></div> or <div role="rowheader"></div> </div> </div>
This presented a problem for us because the FlexGrid is composed of panels (topLeft, columnHeaders, rowHeaders, cells, etc). Plus, the FlexGrid did not even have “row” elements. Cell elements were direct children of the panels.
We addressed these problems by:
The new version of the grid has more HTML elements than previous ones, but performance is better in many cases because it is easier to reorder rows than cells while scrolling vertically. Also, the new row elements provide more control over styling, since the row elements can be used in CSS selectors.
The ARIA recommendation for grid elements includes the “gridcell” role for regular cells and the “columnheader” and “rowheader” roles for column and row header cells. If no elements are assigned the header roles, screen readers will announce the content of the first cell in each row or column as their headers.
The FlexGrid addresses this issue by creating an extra invisible row at the top of the grid. This invisible row contains the column headers, in elements with the “columnheader” role. The content of the column header cells is taken from the “header” property of the grid’s columns.
The FlexGrid also provides a “rowHeaderPath” property that allows you to specify a binding that will be used to provide row header information. If this property is set to a non-null value, the grid will create an extra invisible column with the “rowheader” role.
This is important because in most applications, the first column does not contain information suitable to be used as row headers. And even if it did, the grid virtualizes cells so the first visible cell in a row may not correspond to the first bound column.
The FlexGrid used to render frozen cells after regular cells. This was done to ensure they displayed ‘above’ regular cells. Unfortunately, that meant screen readers would ‘see’ the cells in the wrong order, which was confusing for users with accessibility needs.
We addressed this problem by rendering the cells in the natural order, and using the ‘z-index’ style attribute to show the frozen cells above regular ones. This change made the code and the DOM simpler, at the expense of some additional styling.
Most importantly, it made screen readers work properly on grids with frozen cells.
The FlexGrid uses Microsoft Excel as a model for many of its operations, including keyboard handling.
This is a good approach in most cases, but we made a few changes to the keyboard handling logic to better accommodate accessibility:
The other keyboard commands used by the FlexGrid follow the ARIA recommendations and are largely compatible with Excel.
The table below summarizes the FlexGrid keyboard commands:
he table below summarizes the FlexGrid keyboard commands:
|Keyboard Combination||Action Performed|
|Shift + Space||Select row|
|Control + Space||Select column|
|Space||Start editing or toggle checkbox|
|Control + A||Select the entire grid contents|
|Left/Right||Select the cell to the left/right of the selection|
|Shift + Left/Right||Extend the selection to include the next cell to the left/right of the selection|
|Up/Down||Select the next cell above or below the selection|
|Shift + Up/Down||Extend the selection to include the cell above or below the selection|
|Alt + Up/Down||Drop down the listbox editor for the current cell|
|PageUp/Down||Select the cell one page above or below the selection|
|Shift + PageUp/Down||Extend the selection to include the cell one page above or below the selection|
|Alt + PageUp/Down||Move the selection to the first or last row|
|Shift + Alt + PageUp/Down||Extend the selection to include the first or last row|
|Home/End||Move the selection to the first or last column|
|Shift + Home/End||Extend the selection to include the first or last column|
|Ctrl + Home/End||Move the selection to the first/last row and column|
|Shift + Ctrl + Home/End||Extend the selection to include the first/last row and column|
|Escape||Cancel current cell or row editing operation|
|Tab||Move the selection to the next focusable element on the page (by default, can be overridden using the keyActionTab property)|
|Enter||Exit editing mode and move the selection to the cell below the current one (by default, can be overridden using the keyActionEnter property)|
|Delete, Backspace||Delete the currently selected rows (if the allowDelete property is set to true), or clear the content of the selected cells (if the values are not required).|
|Control + C or Control + Insert||Copy the selection to the clipboard (if the autoClipboard property is set to true)|
|Control + V or Shift + Insert||Paste the content of the clipboard into the selected area (if the autoClipboard property is set to true)|
*Modified in the latest version to improve accessibility
The FlexGrid code avoids adding event handlers to child elements. Instead, most events listeners are attached to the grid’s host element, and forward the commands to the appropriate child elements based on hit-testing logic.
This presents a problem in some accessibility scenarios that expect to be able to get references to child elements and fire events in code, by invoking the child element’s “click” method directly. In this case, the grid does receive the click event as usual, but without valid mouse coordinates and without “mousedown” and “mouseup” events before the click.
To accommodate this scenario, we added a new HitTest constructor that takes an element as a parameter and builds the necessary hit-test information so the grid can honor click events fired in code. For example, you could generate a “click” event on a column header using this code:
// Get the header cell var headerCell = grid.columnHeaders.getCellElement(0, 0); // Invoke the “click” event on the header cell headerCell.click();
Or you could drop down the filter editor for a given column using this code:
// Get the filter glyph element using a CSS selector var selector = '.wj-flexgrid .wj-colheaders .wj-cell .wj-elem-filter'; var filterBtn = grid.hostElement.querySelector(selector); columnHeaders.getCellElement(0, 0); // Invoke the “click” event on the filter glyph filterBtn.click();
You can follow the same approach to simulate clicks and select cells, expand/collapse nodes, drop down list boxes associated with cells, etc.
Focus handling was by far the toughest challenge. In previous versions, the grid used a hidden element to grab the focus and prevent browsers from trying to scroll the entire grid into view. The selection was handled by the grid itself, independent from the focus.
This approach made it impossible for assistive technologies to examine and navigate the grid, since they rely on getting or setting the document’s activeElement, expect focusable elements to have tabIndex values greater than -1, etc.
We changed the grid to make sure the currently selected cell element does contain the focus, and it corresponds to the grid’s selection. If the grid has no selection, or if the selected cell is scrolled away from view, the grid falls back to use the original hidden element so the focus stays within the grid.
As we said earlier, implementing accessibility requires a lot more than just adding some ARIA attributes to controls elements. We spent a lot of time testing FlexGrid’s accessibility support. We did not work alone on this.
Some of our customers have accessibility teams who worked with us to fine-tune the accessibility features and test them with different assistive technologies. Their help was invaluable!
One of the basic tools you can use to test your apps is Chrome’s accessibility audit tool. You can find it in the Audits tab under development tools. Here’s a screenshot:
When you click the Run button, Chrome will show a list of accessibility problems. For example:
These warnings are very useful when checking your apps for accessibility issues. There may be some false alarms, but the tool does catch many real issues. It’s a good idea to get into the habit of using the tool and learning how to avoid common problems.
The accessibility audit is especially useful when you start adding ARIA attributes to your applications and components. It knows the WAI-ARIA 1.0 spec and will warn you about implementation errors.
Once the app is running, you can test its accessibility using one (or several) of the assistive tools available. Some of the most popular are:
Our benchmark app was designed to compare the performance of different grid and chart controls, but you can also use it to test accessibility support. The benchmark app is available here.
If you open the benchmark app in Chrome and install Chrome’s Vox extension, test the accessibility of the grids by selecting them and using Vox’s commands. For example, select a cell and press
Shift+Alt+ followed by TH to read the cell’s row and column headers
If you open the benchmark app in Edge and run the Narrator, test the accessibility of the grids by selecting them and using Narrator’s commands. For example, select a cell and press
CapsLock+F9 to read the column header, or
CapsLock+F10 to read the row header
The accessibility tools have commands for reading and navigating the elements on the page. But you will notice that not all grids provide the same level of accessibility support. We believe the FlexGrid is among the best, and will work to keep it that way.
If accessibility is important to you, and you have any feedback you’d like to share with us, we’d love to hear from you.
We highly recommend that you try a built-in screen reader like Windows Narrator or Mac VoiceOver. Then navigate to this fiddle to experience a page using the screen reader.
Here is a video of what it’s like to use the fiddle with Jaws: