Skip to main content

Accessibility in DHTMLX Grid

DHTMLX Grid is built to be usable by everyone, including people who rely on keyboards, screen readers, screen magnification, or high-contrast display modes. WAI-ARIA semantics are part of the rendered markup, and a single, coherent focus model spans the header, body, and footer.

Target conformance

DHTMLX Grid is designed to meet WCAG 2.2 Level AA, Section 508 of the U.S. Rehabilitation Act, and EN 301 549 (the technical baseline of the European Accessibility Act). Because WCAG 2.2 AA also satisfies ADA and Section 508 expectations, a single conformance target covers the major regulatory requirements.

Conformance is reported criterion by criterion rather than as a blanket claim. The detailed Accessibility Conformance Report (VPAT® 2.5) is available: Accessibility Conformance Report.

At a glance​

AreaSupport
Keyboard operationFull: cell navigation, editing, sorting, range selection, tree expand/collapse, and clipboard all have keyboard equivalents. Cell navigation works in every configuration, with or without a selection module
WAI-ARIA semanticsBuilt-in (grid / treegrid model), always rendered
Focus modelTab / Shift + Tab move cell by cell through the header, body, and footer and leave the grid at its ends; the arrow keys move within and between the zones
Selection modelOptional: single-cell/row (selection) or spreadsheet-style range (blockSelection), for selecting cells and ranges
Screen readersTested with NVDA, JAWS and VoiceOver
Visual accessibilityDedicated light and dark high-contrast themes (color-blind friendly, AA contrast, 16px base) and non-color cues
Text resize / zoomLayout remains operable up to 400% zoom / text spacing overrides
CustomizationPublic announce() API for dynamic messages, and localizable aria_* strings for every built-in screen-reader description
StandardsWCAG 2.2 AA, Section 508, EN 301 549

Use this page to verify the component against your own accessibility checklist, and to learn how to configure the Grid for an accessible deployment.

Scope​

This guide covers the Grid widget itself:

  • the data body: cells and rows, including tree mode (type: "tree")
  • the column header: sortable headers and in-header filters
  • the footer: summaries and footer filters
  • the inline editors
  • the keyboard model that connects these zones.

As with any embeddable component, the accessibility of the final page also depends on the host application (see Host-page responsibilities).

Setting up an accessible Grid​

The Grid ships accessible by default: WAI-ARIA output is emitted automatically, and keyboard navigation is on (keyNavigation: true), so the whole cell surface can be reached and walked with the keyboard in any configuration. For a fully WCAG 2.2 AA-compliant deployment, the only other thing you need is an AA-contrast theme. The recommended configuration is:

// 1. WAI-ARIA output is always on, and keyboard navigation is enabled by default - no action needed
const grid = new dhx.Grid("grid_container", {
columns: [/* ... */],
data: dataset,
keyNavigation: true // true - by default
});

// 2. Select an AA-conformant theme
dhx.setTheme("contrast-light"); // or "contrast-dark"

The theme can also be applied through the data-dhx-theme attribute of the container or of the root element; see the Themes overview for all the options.

Add a selection module only when users need to select cells or ranges; see the next section.

Keyboard navigation with and without selection​

Without a selection module​

A grid configured without selection and blockSelection is still fully navigable. The active cell moves with the arrow keys, Home / End, Ctrl + Home / End, Page Up / Page Down and Tab, and it is scrolled into view, including next to frozen columns and rows. Moving it selects nothing: aria-selected is not set, no selection event fires, and no selection styling is painted. This is the configuration to use for a read-only table:

// A read-only grid that can still be walked cell by cell
const grid = new dhx.Grid("grid_container", {
columns: [/* ... */],
data: dataset
});
note

Opening an editor from the keyboard (Enter, F2, Space) requires only editable: true; no selection module is needed.

With a selection module​

When a selection module is enabled, the selection moves together with the active cell and is exposed through aria-selected. The two modules are independent and drive different ARIA output and shortcut semantics.

1. Cell / row selection (selection)​

A single active cell (or row) moves with the arrow keys. This populates aria-selected on the focused cell or row. Extending the selection with Shift is enabled only when multiselection: true; without it, Shift+arrow moves the active cell.

// Single active cell, navigable with arrows / Tab / Home / End / Page Up·Down
const grid = new dhx.Grid("grid_container", {
columns: [/* ... */],
data: dataset,
selection: "complex", // "cell" | "row" | "complex"
multiselection: true, // enables Shift+Arrow multi-select
keyNavigation: true, // default
sortable: true // default, keyboard sort in headers
});
selection valueMeaning
"cell"One active cell; Shift+arrow extends when multiselection: true
"row"One active row; arrows move the whole row, aria-selected is on the row
"complex"Cell- and row-style selection combined
trueEquivalent to cell selection
falsy / unsetSelection (and aria-selected) disabled; keyboard navigation still works
2. Range / block selection (blockSelection)​

Spreadsheet-style rectangular ranges. The arrow keys move the range anchor; Shift+arrows grow or shrink the rectangle; Delete clears the range (when editing is enabled). This applies in "range" mode.

// Google-Sheets-style range selection
const grid = new dhx.Grid("grid_container", {
columns: [/* ... */],
data: dataset,
blockSelection: true, // range mode (Shift+Arrow grows the rectangle)
editable: true, // allows Delete to clear the range
keyNavigation: true
});
blockSelection valueModeKeyboard effect
truerangeArrows move the range; Shift+arrows extend the rectangle; Delete clears it

Both modules use the same navigation keys; the Grid responds to whichever module is active.

TreeGrid​

TreeGrid mode (type: "tree") adds role="treegrid", aria-level and aria-expanded, and enables arrow-key expand/collapse:

const grid = new dhx.Grid("grid_container", {
columns: [/* ... */],
data: dataset,
type: "tree",
selection: "complex"
});

The sections below describe each capability in detail.

WAI-ARIA attributes​

WAI-ARIA roles and attributes are added to the component markup automatically; they are always rendered and cannot be turned off. The Grid exposes itself to assistive technology as an interactive grid (or treegrid) of rows and cells, with a separate group for the header and footer. The semantics are applied per structural part of the widget, so each part is announced with the correct role and state.

Grid container​

The following table lists the container role and the grid-wide attributes, which describe the grid as a whole. They are applied to the content element of the grid, which holds the header, body, footer and frozen zones:

SelectorRole / attributePurpose
.dhx_grid-contentrole="grid"Standard grid
.dhx_grid-contentrole="treegrid"TreeGrid (type: "tree")
.dhx_grid-contentaria-labelAccessible name of the grid, set through the ariaLabel property
.dhx_grid-contentaria-rowcountTotal number of data rows
.dhx_grid-contentaria-colcountNumber of visible columns
.dhx_grid-contentaria-readonly"true" when the grid is not editable
.dhx_grid-contentaria-multiselectable"true" when multi-selection is enabled

Rows and cells​

The following table lists the roles and the position and state attributes applied to data rows and cells, which let assistive technology announce where the user is:

SelectorRole / attributePurpose
Data rowrole="row"A row of the grid
Data rowaria-rowindex1-based row position
Data cellrole="gridcell"A data cell
Data cellaria-colindex1-based column position
Data cellaria-readonly"true" when the cell is not editable
Data cellaria-selectedSelection state of the cell, when a selection module is enabled
Data celltabindex"0" on exactly one cell (the active cell), "-1" on the rest, so only one body cell is in the page tab sequence (roving tabindex)
Merged cellaria-colspan / aria-rowspanNumber of columns / rows the merged cell covers

Tree (TreeGrid) rows​

The following table lists the hierarchy attributes applied to tree rows and the role on the expand/collapse toggle in TreeGrid mode (type: "tree"), which convey the row's depth and open/closed state:

SelectorRole / attributePurpose
Tree rowaria-levelDepth of the row in the hierarchy
Tree rowaria-expandedOpen/closed state of a branch with children
Tree rowaria-selectedSelection state of the row
Expand/collapse togglerole="button" + aria-label"Expand group" / "Collapse group"

The following table lists the roles and attributes applied to the header and footer groups, rows, and cells, which expose the column headers, sort state, and filter controls:

SelectorRole / attributePurpose
Header/footer grouprole="rowgroup"Groups the header or footer rows
Header/footer rowrole="row" + aria-rowindexA header or footer row
Header cellrole="columnheader" + aria-sortColumn header; aria-sort is none / ascending / descending and is present only on sortable columns
Footer cellrole="gridcell" + aria-colindexA footer (summary) cell
Content (filter) cellrole="gridcell"Header/footer cell hosting a filter control
Sort affordancerole="button" + aria-label="Sort by …"Keyboard- and pointer-activatable sort trigger

Editors and filters​

In-place editor inputs and header/footer filters receive an accessible name derived from column header text, so screen-reader users hear which column they are editing or filtering. For example, a filter input is labeled "Filter {column}" and a date filter "Filter by date: {column}".

Hidden and decorative elements​

Resizer grips, sort icons, sort-order counters, drag ghosts, drop indicators, and the selection overlay are removed from the accessibility tree with aria-hidden="true" / role="presentation", so screen readers are not cluttered with redundant markup. The hidden focus sentinels that let the keyboard enter and leave the widget are placed outside the grid role and are not exposed as controls.

Screen-reader support​

DHTMLX Grid is tested against the most widely used assistive technologies:

Screen readerBrowserPlatform
NVDAFirefoxWindows
JAWSChromeWindows
VoiceOverSafarimacOS

Rows and cells carry their position (aria-rowindex / aria-colindex) against the grid totals (aria-rowcount / aria-colcount). Because these are absolute positions in the dataset rather than positions in the DOM, they stay correct while rows and columns are virtualized: a screen reader announces "row 4,812 of 50,000" even though only the visible window exists in the markup.

The rest of the state travels the same way, on the element it belongs to:

StateExposed throughRead on
Selectionaria-selectedThe selected cell, or the row in selection: "row" mode, when a selection module is enabled
Editabilityaria-readonlyThe grid container and each cell
Sort directionaria-sort (none / ascending / descending)The header of a sortable column
Hierarchyaria-level, aria-expandedTree rows (type: "tree")
Merged cellsaria-colspan, aria-rowspanThe merged cell
Multi-selection capabilityaria-multiselectableThe grid container

Editors and filters take their accessible name from the column header text, so the user always hears which column is in play. Decorative markup (resizer grips, sort icons, drag ghosts, drop indicators, the selection overlay) is hidden from the accessibility tree, so nothing redundant is announced.

Announcing dynamic changes​

For things that happen and have no permanent element to label (a completed load, a filter result, a corrected value), the Grid writes text into a visually hidden live region (role="status", aria-live="polite", aria-atomic="true") that the screen reader reads aloud without moving focus. The region is available to your own code through the announce() method:

grid.announce("5 rows imported");

The message is always polite: the screen reader finishes its current sentence first, so an announcement never interrupts the user.

The Grid already announces the following out of the box:

EventAnnounced text
Sorting by a columnSorted by {column}, ascending / Sorted by {column}, descending
Applying a filter{count} rows match the filter
Clearing the filterFilter cleared, {count} rows
Loading data{count} rows loaded
Entering an out-of-range number in an editorValue must be between {min} and {max} and, once it is fixed, Value corrected to {value}

Translatable screen-reader names​

The strings that a screen reader hears come from the Grid locale, so they are translated with the rest of the UI. They fall into two groups:

  • announcements: aria_sortedAscending, aria_sortedDescending, aria_filterApplied, aria_filterCleared, aria_rowsLoaded, aria_valueOutOfRange, aria_valueBelowMin, aria_valueAboveMax, aria_valueClamped;
  • accessible names: aria_sortBy, aria_filter, aria_filterByDate, aria_expandGroup, aria_collapseGroup, aria_expandRow, aria_collapseRow, aria_editContent, aria_subRow, aria_rowId, and the drag-panel names.

Override them like any other locale label, before the Grid is initialized:

dhx.i18n.setLocale("grid", {
aria_sortBy: "Sortieren nach {column}",
aria_rowsLoaded: "{count} Zeilen geladen"
});

const grid = new dhx.Grid("grid_container", config);

Placeholders in curly braces ({column}, {count}, {min}, {max}, {value}, {id}) are substituted at runtime and must be kept in the translated string. See the Localization article for the full locale workflow.

Keyboard navigation​

Keyboard navigation is on by default (keyNavigation: true); set keyNavigation: false to turn it off. It works with or without a selection module: without one, the navigation keys move only the active cell; with one, the selection moves together with the active cell. See Keyboard navigation with and without selection.

Focus enters the Grid through hidden focus sentinels placed before the header and after the footer: Tab from the page lands on the header, and Shift + Tab lands on the footer (or on the header, when there is no footer). Inside the Grid, Tab and Shift + Tab move cell by cell, wrapping rows, through the header, the body, and the footer, in visual order. Tab on the last cell of the Grid and Shift + Tab on the first header cell leave the Grid in every configuration, including one with frozen columns (leftSplit / rightSplit).

The arrow keys move within a zone and between the zones: ↑ from the first body row enters the header, and ↓ from the last body row enters the footer.

In the grid below, no selection module is enabled, so the arrow keys move the active cell, which is marked only by the dashed focus outline:

Grid with focus and without selection

The High-contrast themes section illustrates two more ways focus is indicated: the solid border of cells selected with the keyboard when selection is enabled, and the focus ring of a column header.

Shortcuts are organized into zones (body, header, footer) and resolved by where focus currently is. The full reference is in the Keyboard navigation article; the tables below summarize it.

Grid body​

KeysActionRequires
↑ / ↓ / ← / →Move the active cell one row/column (↑ from the first row enters the header; ↓ from the last row enters the footer when a footer exists)—
Ctrl + arrowJump the active cell to the first/last cell in that direction—
Shift + arrowExtend the selection by one cellselection with multiselection: true, or blockSelection range
Ctrl + Shift + arrowExtend the selection to the edgeas above
Home / EndMove to the first / last column of the current row—
Ctrl + Home / Ctrl + EndMove to the first / last cell of the grid—
Shift + Home / End, Ctrl + Shift + Home / EndExtend the selection to the row/grid extentextend-capable modes
Page Up / Page DownMove the active cell up / down by one page of visible rows—
Shift + Page Up / Page DownExtend the selection by one pageextend-capable modes
EnterOpen the editor (or toggle a boolean cell); when editing, commit and closeeditable
F2Open the editor of the active cell (non-boolean)editable
SpaceToggle a boolean celleditable
EscapeCancel editing without savingeditable
Tab / Shift + TabMove to the next / previous cell, wrapping rows; at the ends, Tab moves to the footer (or leaves the grid, when there is no footer) and Shift + Tab moves to the header—
DeleteClear the selected rangeblockSelection range mode and editable
Ctrl + Z / Ctrl + Shift + ZUndo / RedoHistory module
Ctrl + EnterExpand / collapse the row (type: "tree")TreeGrid
→ / ← (tree column)Expand / collapse a branch, or move to first child / parentTreeGrid
KeysAction
← / →Move between header cells (colspan-aware)
↑ / ↓Move between header rows (multi-row header); ↑ from the first row has no effect, ↓ from the last row moves into the body
Enter / SpaceSort by the column; on a filter cell, Enter activates the filter control
Shift + EnterToggle multi-sort for the column (requires multiSort)
Tab / Shift + TabMove within the header with row wrapping; at the ends, Tab moves to the body and Shift + Tab leaves the grid
EscapeDeactivate a filter control (restoring focus to its cell), or return focus to the body
KeysAction
← / →Move between footer cells (colspan-aware)
↑ / ↓Move between footer rows (multi-row footer); ↑ from the first row moves into the body, ↓ from the last row has no effect
EnterActivate a footer filter control
Tab / Shift + TabMove within the footer with row wrapping; at the ends, Tab leaves the grid and Shift + Tab moves to the body
EscapeDeactivate a filter control, or return focus to the body
note

Navigation is span-aware: movement across merged (colspan/rowspan) header and footer cells stays predictable, and the logical navigation row is preserved. When focus reaches an off-screen (virtualized) column, the Grid scrolls it into view automatically.

Low vision and color​

High-contrast themes​

Light and dark high-contrast themes ship with the library, contrast-light and contrast-dark, activated by dhx.setTheme("contrast-light") / dhx.setTheme("contrast-dark") or by setting data-dhx-theme="contrast-light" / data-dhx-theme="contrast-dark" on the container or the root element. Both meet WCAG AA contrast, are color-blind friendly, and raise the base font size to 16px. See the Themes guide and the Light High Contrast / Dark High Contrast pages for details.

The contrast-light theme draws dark text and borders on a white background. In the grid below, selection and multiselection are enabled, so focus moves through the cells with Tab and the arrow keys, and Shift + arrow extends the selection from the focused cell. Each selected cell is marked by a solid border:

Grid in the contrast-light theme

The contrast-dark theme draws light text and borders on a dark background. When focus is on a column header, the header is marked by the focus ring:

Grid in the contrast-dark theme

Other low-vision support​

  • Color is never the only signal. Sort direction is carried by an arrow glyph and by aria-sort, selection by aria-selected alongside the highlight, editability by aria-readonly, and hierarchy by aria-level and aria-expanded.
  • Zoom and reflow. The grid layout remains operable when the page is zoomed up to 400%.
  • Text spacing. Applying WCAG text-spacing overrides does not clip or overlap text in grid cells, column headers or footer summaries.
  • Visible focus. Focus is tracked per zone by the roving-tabindex model, so one body cell holds the roving tab stop and moves predictably with the arrow keys. With selection: "cell" / "complex" or blockSelection, the active cell is marked by a persistent 2px solid selection border in the theme primary color, and no second focus outline is drawn over it. With selection: "row", and in a grid without a selection module, the focused cell is marked by a dashed focus outline. Header and footer cells show a 2px focus ring, and filter inputs and open editors an inset ring.
  • Scrolling into view. When focus reaches an off-screen row or column, the Grid scrolls it into view and compensates for frozen columns and rows and for the header and footer height, so the focused cell is never left behind a frozen zone.

Host-page responsibilities​

A few accessibility requirements live at the page level, not inside the component. Make sure the host document:

  • sets a document language, e.g. <html lang="en">;
  • provides a page <h1> and wraps the grid in an appropriate landmark (e.g. <main>);
  • gives each grid an accessible name with the ariaLabel property where multiple widgets share a page.

Testing and methodology​

Accessibility is validated continuously, and against the component source rather than a single demo page, so the results hold for every configuration the Grid can be put into:

  1. Automated testing with axe-core across representative configurations, among them the plain grid, TreeGrid mode, header and footer filters, inline editing, frozen columns and rows, and the high-contrast themes.
  2. Manual review of the source: the roles, states and accessible names emitted per part of the widget; the shortcut registry and focus model (zones, the roving tab stop, the sentinels that enter and leave the widget, and the behavior of each binding in each selection mode); and the theme tokens, with contrast ratios computed for the default light, dark, contrast-light and contrast-dark themes.
  3. Manual testing: keyboard-only walkthroughs of every zone, screen-reader passes with NVDA, JAWS and VoiceOver, and checks under color-vision-deficiency emulation, 200% / 400% zoom and WCAG text-spacing overrides.
  4. Conformance reporting: results are published openly, criterion by criterion, in the Accessibility Conformance Report, including the criteria the Grid only partially meets.

Resources​