From f1b90253c2cf47ef35ec9acc15ffef8783a874c9 Mon Sep 17 00:00:00 2001 From: Sorokin-Oleg Date: Thu, 3 Sep 2026 17:26:10 +0300 Subject: [PATCH 01/10] [dev] Grid. Added the Conformance report (VPAT) (DHX-5290) --- docs/common_features/accessibility_support.md | 2 +- docs/grid/accessibility.md | 71 +++++- docs/grid/accessibility_conformance_report.md | 215 ++++++++++++++++++ sidebars.js | 1 + 4 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 docs/grid/accessibility_conformance_report.md diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index ac6f4f51f..69c73434f 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -42,7 +42,7 @@ You can find the following **roles** and **attributes** in the DOM: In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. :::info -For the complete picture — the ARIA model, the keyboard zones (header/body/footer), the focus model, and configuration recipes — see the dedicated [Grid accessibility](grid/accessibility.md) guide. +For the complete picture — the ARIA model, the keyboard zones (header/body/footer), the focus model, and configuration recipes — see the dedicated [Grid accessibility](grid/accessibility.md) guide. Criterion-by-criterion conformance with WCAG 2.2 AA, Section 508 and EN 301 549 is documented in the [Accessibility Conformance Report](grid/accessibility_conformance_report.md). ::: ### Chart diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index afa742ac3..03ce75101 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -8,6 +8,15 @@ description: You can learn about accessibility and keyboard navigation in DHTMLX DHTMLX Grid is built to be operated entirely from the keyboard and to expose its structure and state to assistive technology. WAI-ARIA semantics are part of the rendered markup, and a single, coherent focus model spans the header, body, and footer. The semantics are always present — there is **no** configuration flag to disable them. +:::info 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](grid/accessibility_conformance_report.md). +::: + + + ## Capabilities | Area | Support | @@ -77,7 +86,7 @@ The following table lists the roles and attributes applied to the header and foo | Selector | Role / attribute | Purpose | | -------- | ---------------- | ------- | -| Header/footer group | `role="rowgroup"` + `aria-rowcount` | Groups the header or footer rows | +| Header/footer group | `role="rowgroup"` | Groups the header or footer rows | | Header/footer row | `role="row"` + `aria-rowindex` | A header or footer row | | Header cell | `role="columnheader"` + `aria-sort` | Column header; `aria-sort` is `none` / `ascending` / `descending` | | Footer cell | `role="gridcell"` + `aria-colindex` | A footer (summary) cell | @@ -196,12 +205,38 @@ Navigation is **span-aware**: movement across merged (colspan/rowspan) header an ## Assistive technology -What the Grid exposes to assistive technology is driven entirely by the ARIA markup above. Rows and cells carry their position (`aria-rowindex` / `aria-colindex`) against the grid totals (`aria-rowcount` / `aria-colcount`), so position is announced even when rows are virtualized. Selection is exposed through `aria-selected`, editability through `aria-readonly`, sort state through `aria-sort`, and — in tree mode (`type: "tree"`) — hierarchy through `aria-level` and `aria-expanded`. +DHTMLX Grid is tested against the most widely used assistive technologies: + +| Screen reader | Browser | Platform | +| ------------- | ------- | -------- | +| NVDA | Firefox | Windows | +| JAWS | Chrome | Windows | +| VoiceOver | Safari | macOS | + +What the Grid exposes to them is driven entirely by the ARIA markup above — there is no separate announcement layer to configure. + +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: + +| State | Exposed through | Read on | +| ------ | --------------- | ------- | +| Selection | `aria-selected` | The focused cell, or the row in `selection: "row"` mode | +| Editability | `aria-readonly` | The grid container and each cell | +| Sort direction | `aria-sort` (`none` / `ascending` / `descending`) | The column header | +| Hierarchy | `aria-level`, `aria-expanded` | Tree rows (`type: "tree"`) | +| Multi-selection capability | `aria-multiselectable` | The 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. ## High contrast and focus -- **High-contrast themes.** Light and dark high-contrast themes ship with the library (`contrast-light` and `contrast-dark`), activated by setting `data-dhx-theme="contrast-light"` or `data-dhx-theme="contrast-dark"`. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. -- **Visible focus.** Focus is tracked per zone by the roving-tabindex model, so the active cell is the single tab stop and moves predictably with the arrow keys. +- **High-contrast themes.** Light and dark high-contrast themes ship with the library (`contrast-light` and `contrast-dark`), activated by setting `data-dhx-theme="contrast-light"` or `data-dhx-theme="contrast-dark"`. Both meet WCAG AA contrast and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. + + +- **Visible focus.** Focus is tracked per zone by the roving-tabindex model, so the active cell is the single tab stop and moves predictably with the arrow keys. The active cell is marked by a persistent 2px solid border in the theme primary color; header and footer cells show a 2px focus ring, and filter inputs and open editors an inset ring. Because keyboard navigation moves the selection, the focus position stays visible throughout arrow-key navigation. +- **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 height, so the focused cell is never left behind a frozen zone. ## Configuration recipes @@ -233,13 +268,8 @@ new dhx.Grid("grid_container", { // WAI-ARIA semantics are always emitted — there is no flag to toggle them. ~~~ -Related articles: - -- [Keyboard navigation](grid/configuration.md#keyboard-navigation) -- [keyNavigation](grid/api/grid_keynavigation_config.md) -- [selection](grid/api/grid_selection_config.md) -- [blockSelection](grid/api/grid_blockselection_config.md) -- [TreeGrid mode](grid/treegrid_mode.md) + ## Host-page responsibilities @@ -249,6 +279,23 @@ A few accessibility requirements live at the page level, not inside the componen - provides a page `

` and wraps the grid in an appropriate landmark (e.g. `
`); - gives the grid container an accessible name where multiple widgets share a page. -## Reference +## 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](https://github.com/dequelabs/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](grid/accessibility_conformance_report.md) — including the criteria the Grid only partially meets. +## Resources + +- [Accessibility Conformance Report (VPAT 2.5)](grid/accessibility_conformance_report.md) +- [Keyboard navigation](grid/configuration.md#keyboard-navigation) +- [keyNavigation](grid/api/grid_keynavigation_config.md) +- [selection](grid/api/grid_selection_config.md) +- [blockSelection](grid/api/grid_blockselection_config.md) +- [TreeGrid mode](grid/treegrid_mode.md) +- [Accessibility support across DHTMLX Suite](common_features/accessibility_support.md) +- [WCAG 2.2](https://www.w3.org/TR/WCAG22/) - [WAI-ARIA Authoring Practices: Grid / Treegrid](https://www.w3.org/WAI/ARIA/apg/patterns/) diff --git a/docs/grid/accessibility_conformance_report.md b/docs/grid/accessibility_conformance_report.md new file mode 100644 index 000000000..8a6d6a28c --- /dev/null +++ b/docs/grid/accessibility_conformance_report.md @@ -0,0 +1,215 @@ +--- +sidebar_label: Conformance report (VPAT) +title: JavaScript Grid - Accessibility Conformance Report +description: You can explore the accessibility conformance report (VPAT 2.5) for DHTMLX Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +--- + +# Accessibility Conformance Report + +This report documents how DHTMLX Grid conforms to the major accessibility standards. It follows the **VPAT® 2.5 (International Edition)** structure, so you can map it directly against your own procurement or compliance checklist. For a task-oriented overview of the accessibility features and how to configure them, see the [Accessibility](grid/accessibility.md) guide. + +**Name of Product/Version:** DHTMLX Grid v9.3.10 + +**Report Date:** September 2026 + +**Product Description:** DHTMLX Grid is an interactive JavaScript data grid component for building tabular interfaces in a web browser. It renders a column header, a virtualized data body and a footer, and supports sorting, filtering, inline editing, cell and range selection, frozen columns and rows, merged cells, and a hierarchical TreeGrid mode. It is embedded by a host web application. + +**Contact Information:** [info@dhtmlx.com](mailto:info@dhtmlx.com) + +## Notes + +- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output active (always on - there is no flag to disable it), and an AA-conformant colour scheme selected through `data-dhx-theme="contrast-light"` or `data-dhx-theme="contrast-dark"`. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. +- DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities - the page title, ``, skip/bypass mechanisms, and site-wide navigation - belong to the host application and are reported as *Not Applicable* with an explanation. +- In the WCAG tables, only the **Web** response is populated. The *Electronic Docs*, *Software*, *Closed*, and *Authoring Tool* rows are *Not Applicable*: the product is not standalone (non-web) software, closed functionality, an authoring tool, or a piece of electronic documentation. + +**Evaluation Methods Used:** Testing was performed using a combination of automated testing (axe-core in a headless browser), manual code review of the component source and stylesheets (the roles and ARIA attributes emitted per structural part, the keyboard shortcut registry and focus model, the editor and filter naming, and the theme colour tokens, with contrast ratios computed from those tokens for the default, dark, `contrast-light` and `contrast-dark` themes), assistive-technology testing (manual passes with **NVDA + Firefox** and **JAWS + Chrome** on Windows and **VoiceOver + Safari** on macOS), and browser overrides (colour-vision-deficiency emulation, 200% / 400% zoom, and WCAG text-spacing overrides). + +## Applicable standards / guidelines + +| Standard / Guideline | Included in report | +|---|---| +| Web Content Accessibility Guidelines 2.0 | Level A (No) · Level AA (No) · Level AAA (No) | +| Web Content Accessibility Guidelines 2.1 | Level A (No) · Level AA (No) · Level AAA (No) | +| Web Content Accessibility Guidelines 2.2 | Level A (**Yes**) · Level AA (**Yes**) · Level AAA (No) | +| Revised Section 508 standards (published Jan 18, 2017; corrected Jan 22, 2018) | (**Yes**) | +| EN 301 549 - V3.1.1 (2019-11) and V3.2.1 (2021-03) | (**Yes**) | + +*WCAG 2.0 and 2.1 are not reported separately. Criteria that WCAG marks "2.1 and 2.2" are answered because +they are part of WCAG 2.2 and are referenced by EN 301 549.* + +## Terms + +- **Supports:** The functionality of the product has at least one method that meets the criterion without + known defects, or meets with equivalent facilitation. +- **Partially Supports:** Some functionality of the product does not meet the criterion. +- **Does Not Support:** The majority of product functionality does not meet the criterion. +- **Not Applicable:** The criterion is not relevant to the product. + +## WCAG 2.x report + +DHTMLX Grid is web content, so only the **Web** response is populated below. "Requires an AA-conformant +theme" and "requires `keyNavigation`, on by default" are configuration dependencies, not defects. + +### Table 1: Success Criteria, Level A + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| **1.1.1** Non-text Content | Supports | Icons are rendered as font glyphs, not images of text. Interactive icons carry accessible names: the tree expand/collapse toggle is `role="button"` with `aria-label`, and the sort affordance is `role="button"` with `aria-label="Sort by "`. Decorative parts - 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"` or `role="presentation"`. | +| **1.2.1** Audio-only and Video-only (Prerecorded) | Not Applicable | The product contains no audio or video content. | +| **1.2.2** Captions (Prerecorded) | Not Applicable | No prerecorded multimedia. | +| **1.2.3** Audio Description or Media Alternative (Prerecorded) | Not Applicable | No prerecorded multimedia. | +| **1.3.1** Info and Relationships | Supports | The grid uses `grid` → `rowgroup` → `row` → `gridcell`/`columnheader` roles, and `treegrid` in tree mode. Position is exposed with `aria-rowindex`/`aria-colindex` against `aria-rowcount`/`aria-colcount`; editability with `aria-readonly`; multi-selection capability with `aria-multiselectable`; sort state with `aria-sort`; hierarchy with `aria-level` and `aria-expanded`. ARIA output is always on. | +| **1.3.2** Meaningful Sequence | Supports | DOM and reading order follow the visual order. Rows and columns are virtualized, but `aria-rowindex`/`aria-colindex` carry the absolute position in the dataset rather than the position in the DOM, so sequence is preserved for assistive technology. | +| **1.3.3** Sensory Characteristics | Supports | No instruction or label relies solely on shape, size, or spatial location; interactive elements carry text or an ARIA name. | +| **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. | +| **1.4.2** Audio Control | Not Applicable | The product produces no auto-playing audio. | +| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`). The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End` and `Page Up`/`Page Down` move the selection; `Shift` combinations extend it; `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | +| **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. Each zone is entered and left with `Tab`/`Shift`+`Tab` through hidden focus sentinels, and `Escape` deactivates a header or footer filter control and returns focus to its cell. | +| **2.1.4** Character Key Shortcuts | Supports | No shortcut uses a bare letter, digit or punctuation key. The only letter-based bindings are `Ctrl`+`Z` and `Ctrl`+`Shift`+`Z`; everything else uses arrows, `Enter`, `Escape`, `Tab`, `F2`, `Space`, `Delete`, `Home`/`End` or `Page Up`/`Page Down`. Handlers are additionally gated on the grid holding focus. | +| **2.2.1** Timing Adjustable | Supports | The product sets no time limits: no session timeout, no auto-refresh and no polling. The header filter's 500 ms debounce delays applying a filter, not the user's input. | +| **2.2.2** Pause, Stop, Hide | Supports | There is no auto-updating, moving, blinking or scrolling content. The only motion is hover and state transitions of 0.5 s or less. | +| **2.3.1** Three Flashes or Below Threshold | Supports | The product contains no flashing content. | +| **2.4.1** Bypass Blocks | Not Applicable | Applies at page level; bypass mechanisms belong to the host application. | +| **2.4.2** Page Titled | Not Applicable | Page titles are the host application's responsibility. | +| **2.4.3** Focus Order | Supports | A roving-tabindex model gives each zone a single tab stop, and the tab order follows the visual order: header → body → footer → next element on the page. When focus reaches an off-screen (virtualized) row or column, the grid scrolls it into view. | +| **2.4.4** Link Purpose (In Context) | Not Applicable | The widget's own interface contains no links. Links placed in cells through `htmlEnable` or a column template are rendered as authored, so their text and purpose belong to the host application. | +| **2.5.1** Pointer Gestures | Supports | No operation requires a multipoint or path-based gesture; touch handling reads a single touch point, and every drag operation has a non-drag alternative (see 2.5.7). | +| **2.5.2** Pointer Cancellation | Supports | The primary actions complete on the up-event: sorting, cell and row selection, and tree expand/collapse are all bound to `click`, not to `mousedown`. | +| **2.5.3** Label in Name | Supports | The sort `role="button"` wraps the visible header text and its accessible name is `Sort by `, so the visible label is contained in the name. Filter inputs and the tree toggle present no visible text label that could mismatch their accessible name. | +| **2.5.4** Motion Actuation | Not Applicable | No functionality is operated by device motion or user motion. | +| **3.1.1** Language of Page | Not Applicable | The document language (``) is set by the host application. | +| **3.2.1** On Focus | Supports | Receiving focus never changes context. The date editor's calendar popup opens without moving focus away from the input. | +| **3.2.2** On Input | Supports | Changing a value does not change context. Editing a cell or typing in a filter updates the grid in place, which is the documented purpose of those controls. | +| **3.2.6** Consistent Help (2.2 only) | Not Applicable | The widget provides no help mechanism; this is a page-level concern. | +| **3.3.1** Error Identification | Supports | Input errors are identified to the user: the numeric editor validates against the column's `min`/`max` bounds and marks an out-of-range value, and input that does not fit the column type is rejected at entry. Application-level validation and its error text are owned by the host application, through the cancelable `beforeEditEnd` event. | +| **3.3.2** Labels or Instructions | Supports | In-place editors and header/footer filters take an accessible name from the column header text, so the user hears which column is being edited or filtered - for example `Filter ` and `Filter by date: `. | +| **3.3.7** Redundant Entry (2.2 only) | Not Applicable | The product contains no multi-step process that re-requests previously entered information. | +| **4.1.1** Parsing | Supports | Markup is generated through a virtual-DOM layer and element ids are machine-generated, so tags are well formed and attributes are not duplicated. | +| **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). | + +### Table 2: Success Criteria, Level AA + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| **1.2.4** Captions (Live) | Not Applicable | No live multimedia. | +| **1.2.5** Audio Description (Prerecorded) | Not Applicable | No prerecorded video. | +| **1.3.4** Orientation | Supports | Content is not restricted to a single display orientation. | +| **1.3.5** Identify Input Purpose | Not Applicable | The product's editors collect application data defined by the integrator's columns, not the user's own personal information covered by the WCAG input-purpose list. | +| **1.4.3** Contrast (Minimum) | Supports | The `contrast-light` and `contrast-dark` themes meet AA out of the box: body text is about 8.5:1 and 13.2:1 against the cell background, secondary and additional text about 7.3:1 and 10.2:1, and both themes raise the base font size to 16px. The built-in dark theme also meets AA for body text. Meeting the criterion requires selecting one of these themes - one supported method meets it. | +| **1.4.4** Resize Text | Supports | Text can be resized to 200% without loss of content or functionality; the header, body and footer scale with it and the grid does not break. Typography and row geometry are token-driven (`--dhx-font-size-normal`, `--dhx-line-height-normal`, `--dhx-step-size`, `rowHeight`), and the contrast themes ship a 16px base. | +| **1.4.5** Images of Text | Supports | All text is rendered as real text. Icons are font glyphs; the product uses no raster images. | +| **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. As a data grid, two-dimensional layout is intrinsic to the content, for which the criterion permits two-dimensional scrolling. | +| **1.4.11** Non-text Contrast | Supports | The active cell is marked by a 2px solid border in the theme primary colour - about 3.7:1 against the default light background and about 6.3:1 on `contrast-dark`, both above the 3:1 requirement. Header and footer cells add a 2px focus ring in the same colour. The contrast themes also raise the cell border contrast. | +| **1.4.12** Text Spacing | Supports | Applying WCAG text-spacing overrides does not clip or overlap text in grid cells, column headers or footer summaries. | +| **1.4.13** Content on Hover or Focus | Supports | Overflow tooltips are hoverable, persistent, and dismissible with `Esc` without moving the pointer. Their content repeats the cell's own text, so no information is available on hover alone, and they can be turned off entirely with `tooltip: false`. | +| **2.4.5** Multiple Ways | Not Applicable | Applies to locating pages within a set of web pages; out of scope for a single embedded component (host-application concern). | +| **2.4.6** Headings and Labels | Supports | Column headers carry descriptive accessible names, and every filter and in-place editor exposes an accessible name derived from its column. | +| **2.4.7** Focus Visible | Supports | The active cell carries a persistent 2px solid border drawn in the theme primary colour. Because keyboard navigation moves the selection, the keyboard focus location is visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | +| **2.4.11** Focus Not Obscured (Minimum) (2.2 only) | Supports | A focused cell is never left behind a frozen zone: when the grid scrolls a cell into view it compensates for the left, right, top and bottom splits and for the header height. | +| **2.5.7** Dragging Movements (2.2 only) | Supports | Every drag operation has a non-drag alternative: range selection through `Shift`+arrows, `Ctrl`+`Shift`+arrows, `Shift`+`Home`/`End` and `Shift`+`Page Up`/`Page Down`; column sizing through `adjust` / `adjustColumnWidth`; and column and row order through the corresponding API. | +| **2.5.8** Target Size (Minimum) (2.2 only) | Supports | Interactive targets meet 24x24 CSS px: the sort target is the whole header cell (40px tall by the column width), the boolean cell's hit area is the whole cell, and the column resizer grip and the range fill handle carry an enlarged hit area (visual size unchanged). | +| **3.1.2** Language of Parts | Not Applicable | The widget does not mix languages within its content; language is host-owned. | +| **3.2.3** Consistent Navigation | Not Applicable | Applies across a set of web pages; host-application concern. | +| **3.2.4** Consistent Identification | Supports | Components with the same function are identified consistently across the header, body and footer zones - the same roles, the same naming pattern and the same key bindings. | +| **3.3.3** Error Suggestion | Supports | Where a correction is known, it is applied: numeric input outside the column's `min`/`max` bounds is corrected to the nearest valid value, so a valid value is always produced. Suggestion text for application-level validation is provided by the host application, through `beforeEditEnd`. | +| **3.3.4** Error Prevention (Legal, Financial, Data) | Supports | Actions that change or remove data are reversible and guarded: the History module provides undo and redo for edits, range clearing and clipboard operations, and every such action can additionally be gated by the host through the cancelable `before*` events - among them `beforeEditEnd`, `beforeKeyDown`, `beforeSort`, `beforeResizeStart`, `beforeRowDrag`/`beforeRowDrop` and `beforeColumnDrag`/`beforeColumnDrop`. | +| **3.3.8** Accessible Authentication (Minimum) (2.2 only) | Not Applicable | The product performs no authentication. | +| **4.1.3** Status Messages | Supports | A visually-hidden `aria-live` region announces dynamic changes without moving focus - sort direction changes, filter results and completed loads. State that persists is additionally exposed on the elements themselves: sort direction through `aria-sort`, selection through `aria-selected`, dataset size through `aria-rowcount`/`aria-colcount`, and branch state through `aria-expanded`. | + +## Revised Section 508 report + +WCAG-mapped provisions are answered in the WCAG 2.x report above and referenced here. Chapter 4 (Hardware) +is Not Applicable - DHTMLX Grid is a software component with no hardware or physical controls. + +### Chapter 3: Functional Performance Criteria + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| 302.1 Without Vision | Supports | Operable with a screen reader through the `grid`/`treegrid` model, the position and state attributes, the accessible names on editors and filters, and the keyboard model. See WCAG 1.3.1, 2.1.1, 4.1.2. | +| 302.2 With Limited Vision | Supports | Zoom, text resize and reflow are supported, and AA contrast is met by the `contrast-light` / `contrast-dark` themes, which also raise the base font size to 16px. See WCAG 1.4.3, 1.4.4, 1.4.10, 1.4.11. | +| 302.3 Without Perception of Color | Supports | No information is conveyed by colour alone: sort direction, selection, editability and hierarchy each carry a glyph or an ARIA state alongside the colour. See WCAG 1.4.1. | +| 302.4 Without Hearing | Supports | No information is conveyed by sound. | +| 302.5 With Limited Hearing | Supports | No information is conveyed by sound. | +| 302.6 Without Speech | Supports | No speech input is required to operate the product. | +| 302.7 With Limited Manipulation | Supports | Fully keyboard-operable; no path-based gesture is required, and pointer targets meet 24x24 px. See WCAG 2.1.1, 2.5.1, 2.5.7, 2.5.8. | +| 302.8 With Limited Reach and Strength | Supports | Operable by keyboard; no physical reach, force or timing requirements. | +| 302.9 With Limited Language, Cognitive, and Learning Abilities | Supports | Labels are consistent across zones, key bindings follow the conventions of a spreadsheet grid, and destructive actions are preventable through the cancelable `before*` events and reversible through the History module. See WCAG 3.2.4, 3.3.4. | + +### Chapter 5: Software + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| 501.1 Scope - Incorporation of WCAG 2.0 AA | See WCAG 2.x section | See the WCAG 2.x report above. | +| 502 Interoperability with Assistive Technology | Not Applicable | Applies to non-web (platform) software. DHTMLX Grid is web content that exposes standard DOM and ARIA to the browser accessibility tree; interoperability is covered by the WCAG results (Clause 9 / 501.1). | +| 503 Applications | Not Applicable | Applies to non-web platform software; the product is web content. | +| 504.2 Content Creation or Editing | Not Applicable | DHTMLX Grid is not an authoring tool for web content - it edits tabular application data, not accessibility-relevant web content. | + +### Chapter 6: Support Documentation and Services + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, the selection modes, the high-contrast themes and the focus model, together with the related configuration. | +| 602.3 Electronic Support Documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA. | +| 602.4 Alternate Formats for Non-Electronic Support Documentation | Not Applicable | Documentation is delivered electronically only; there are no non-electronic (print) support documents. | +| 603.2 Information on Accessibility and Compatibility Features | Supports | The product's accessibility features are documented publicly in the [Accessibility](grid/accessibility.md) guide and can be provided by the support channel on request. | +| 603.3 Accommodation of Communication Needs | Supports | Support is provided over a text-based email channel; users interact through their own assistive technology. | + +## EN 301 549 report + +Clause 9 (Web) maps directly to the WCAG 2.x report above and is the substantive assessment for this web +product. Telephony, video, and hardware clauses (Clauses 6, 7, 8, 13) are Not Applicable. + +### Clause 4: Functional Performance Statements + +These mirror the Section 508 Chapter 3 results (same conclusions). + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| 4.2.1 Usage without vision | Supports | See 508 302.1 (screen-reader operable through the grid/treegrid model, position and state attributes, and the keyboard model). | +| 4.2.2 Usage with limited vision | Supports | See 508 302.2 (AA contrast and a 16px base through the contrast themes; zoom and reflow supported). | +| 4.2.3 Usage without perception of colour | Supports | See 508 302.3 (glyphs and ARIA state accompany every colour cue). | +| 4.2.4 Usage without hearing | Supports | No information conveyed by sound. | +| 4.2.5 Usage with limited hearing | Supports | No information conveyed by sound. | +| 4.2.6 Usage with no or limited vocal capability | Supports | No speech input required. | +| 4.2.7 Usage with limited manipulation or strength | Supports | See 508 302.7 (keyboard-operable, no gestures, primary targets 24x24 px or larger). | +| 4.2.8 Usage with limited reach | Supports | No physical reach requirements (software component). | +| 4.2.9 Minimize photosensitive seizure triggers | Supports | No flashing content (see WCAG 2.3.1). | +| 4.2.10 Usage with limited cognition, language or learning | Supports | See 508 302.9 (consistent labels and key bindings; preventable and reversible actions). | +| 4.2.11 Privacy | Supports | Accessibility features use the same interface as everyone else; there is no separate, less-private mode. | + +### Clause 5: Generic Requirements + +**Mostly Not Applicable.** Clause 5 addresses closed functionality and hardware-style requirements. DHTMLX +Grid is open web software that works with the platform and browser assistive technology, so the +closed-functionality sub-clauses, biometrics (5.3), operable parts (5.5), and locking/toggle-control status +(5.6) are Not Applicable. Where a generic requirement is met through normal assistive-technology support, it +is covered by Clause 9 (Web) and the WCAG results. + +### Clause 9: Web + +**See the WCAG 2.x section.** Clauses 9.1.1.1-9.4.1.3 map to the WCAG 2.2 Level A/AA results in Tables 1 +and 2 above. + +### Clause 11: Software + +**Not Applicable (assessed under Clause 9).** DHTMLX Grid is web content evaluated under Clause 9 (Web); +it is not non-web software. The WCAG-mapped software provisions (11.1.1.1-11.4.1.3) are covered by the WCAG +2.x results. 11.8.2 (Accessible content creation) is Not Applicable - the product is not an authoring tool. + +### Clause 12: Documentation and Support Services + +| Criteria | Conformance level | Remarks and explanations | +|---|---|---| +| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, the selection modes, the high-contrast themes and the focus model. | +| 12.1.2 Accessible documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA (see 508 602.3). | +| 12.2.2 Information on accessibility and compatibility features | Supports | Accessibility features are documented publicly and can be surfaced by support on request. | +| 12.2.3 Effective communication | Supports | Support is provided over a text-based email channel; users interact through their own assistive technology. | +| 12.2.4 Accessible documentation | Supports (see WCAG 2.x section) | Same basis as 12.1.2 - the documentation site is maintained to meet WCAG 2.2 AA. | + +## Legal disclaimer + +This report is provided for informational purposes only and describes the accessibility of the named +product version as of the report date. It is provided "as is", without warranty of any kind, and does not +constitute a contractual commitment or guarantee. Accessibility support may change in subsequent releases, +and results apply to the evaluated configuration. "VPAT" and "Voluntary Product Accessibility Template" are +registered service marks of the Information Technology Industry Council (ITI). diff --git a/sidebars.js b/sidebars.js index 8c31662ab..f2b35a3f5 100644 --- a/sidebars.js +++ b/sidebars.js @@ -2989,6 +2989,7 @@ module.exports = { "grid/usage_history", "grid/customization", "grid/accessibility", + "grid/accessibility_conformance_report", "grid/events" ], }, From a199f983bb59bf5f5f641f7f5c6d140a49ac0b47 Mon Sep 17 00:00:00 2001 From: osorokin Date: Fri, 11 Sep 2026 14:24:26 +0300 Subject: [PATCH 02/10] [dev] Fixed accessibility articles after review (DHX-5290) --- docs/common_features/accessibility_support.md | 4 +- docs/grid/accessibility.md | 275 +++++++++++------- docs/grid/accessibility_conformance_report.md | 21 +- 3 files changed, 182 insertions(+), 118 deletions(-) diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index 69c73434f..a2b540b39 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -32,14 +32,14 @@ There are special attributes used in the markup of DHTMLX Suite widgets that mak ### Grid -There are **roles** and **attributes** for elements of grid, sorting, filters, editable cells to enable screen readers to interpret and navigate the columns and rows of the grid (enabled by default). The semantics are always present and there is no flag to disable them. Custom content should be marked manually. +There are **roles** and **attributes** for elements of grid, sorting, filters, editable cells to enable screen readers to interpret and navigate the columns and rows of the grid (enabled by default). Custom content should be marked manually. You can find the following **roles** and **attributes** in the DOM: - role: *grid* (or *treegrid* in the `type: "tree"` mode), *rowgroup*, *row*, *columnheader*, *gridcell*, *button* - aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort*, *aria-selected*, *aria-readonly*, *aria-multiselectable*, and — for tree rows — *aria-level* and *aria-expanded*. -In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. +In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. Dynamic changes - sorting, filtering, data loading - are read out through a visually hidden polite live region, which the application can write to itself; all the built-in screen-reader strings are stored in the `aria_*` locale keys and can be translated. :::info For the complete picture — the ARIA model, the keyboard zones (header/body/footer), the focus model, and configuration recipes — see the dedicated [Grid accessibility](grid/accessibility.md) guide. Criterion-by-criterion conformance with WCAG 2.2 AA, Section 508 and EN 301 549 is documented in the [Accessibility Conformance Report](grid/accessibility_conformance_report.md). diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index 03ce75101..382f49c25 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -6,7 +6,7 @@ description: You can learn about accessibility and keyboard navigation in DHTMLX # Accessibility in DHTMLX Grid -DHTMLX Grid is built to be operated entirely from the keyboard and to expose its structure and state to assistive technology. WAI-ARIA semantics are part of the rendered markup, and a single, coherent focus model spans the header, body, and footer. The semantics are always present — there is **no** configuration flag to disable them. +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. :::info 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. @@ -14,22 +14,28 @@ DHTMLX Grid is designed to meet **WCAG 2.2 Level AA**, **Section 508** of the U. 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](grid/accessibility_conformance_report.md). ::: - - -## Capabilities +## At a glance | Area | Support | | ---- | ------- | | Keyboard operation | Full: cell navigation, editing, sorting, range selection, tree expand/collapse, and clipboard all have keyboard equivalents | -| WAI-ARIA semantics | Built-in (`grid` / `treegrid` model), enabled always — no opt-in flag | +| WAI-ARIA semantics | Built-in (`grid` / `treegrid` model), enabled by default | | Focus model | A single tab stop per zone; focus moves between header, body, and footer | | Selection model | Two modes: single-cell/row (`selection`) and spreadsheet-style range (`blockSelection`) | -| High-contrast display | Light and dark high-contrast themes (`contrast-light` / `contrast-dark`) | +| Screen readers | Tested with NVDA, JAWS and VoiceOver | +| Visual accessibility | Dedicated light and dark high-contrast themes — colour-blind friendly, AA contrast, 16px base — and non-colour cues | +| Text resize / zoom | Layout remains operable up to 400% zoom / text spacing overrides | +| Customization | Public `announce()` API for dynamic messages, and localizable `aria_*` strings for every built-in screen-reader description | +| Standards | WCAG 2.2 AA, Section 508, EN 301 549 | -## Covered areas +Use this page to verify the component against your own accessibility checklist, and to learn how to configure the Grid for an accessible deployment. + + -This documentation covers the Grid widget: +## 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 @@ -39,9 +45,98 @@ This documentation covers the Grid widget: As with any embeddable component, the accessibility of the final page also depends on the host application (see [Host-page responsibilities](#host-page-responsibilities)). -## WAI-ARIA support +## Setting up an accessible Grid {#recommendedconfiguration} + +The Grid ships accessible by default — WAI-ARIA output is emitted automatically and keyboard navigation is on ([`keyNavigation: true`](grid/api/grid_keynavigation_config.md)) — but a fully WCAG 2.2 AA-compliant deployment needs two more things: one of the selection modules enabled, so that keyboard navigation has an active cell to move and to expose through `aria-selected`, and an AA-contrast theme selected. The recommended configuration is: + +~~~jsx +// 1. WAI-ARIA output is enabled by default - no action needed. + +// 2. Enable a selection module so keyboard navigation has an active cell to move +const grid = new dhx.Grid("grid_container", { + columns: [/* ... */], + data: dataset, + selection: "complex", // or blockSelection: true for spreadsheet-style ranges + multiselection: true, // enables Shift+Arrow multi-select + keyNavigation: true // true - by default +}); + +// 3. 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](/themes/) for all the options. + +### Selection modules for keyboard operation {#selectionmodules} + +The keyboard behavior of the body depends on which selection system is enabled. The two 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. + +~~~jsx +// 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` value | Meaning | +| --- | --- | +| `"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 | +| `true` | Equivalent to cell selection | +| *falsy / unset* | Selection (and `aria-selected`) disabled | -WAI-ARIA roles and attributes are added to the component markup automatically and are **on by default** — there is no flag to turn them 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. +#### 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. + +~~~jsx +// 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` value | Mode | Keyboard effect | +| ---------------------- | ---- | --------------- | +| `true` | range | Arrows move the range; Shift+arrows extend the rectangle; Delete clears it | + +Both systems coexist with the same navigation keys; the Grid responds to whichever selection system is active. + +#### TreeGrid + +TreeGrid mode (`type: "tree"`) adds `role="treegrid"`, `aria-level` and `aria-expanded` on top of the selection module, and enables arrow-key expand/collapse: + +~~~jsx +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 and are **enabled by default**. 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 @@ -101,57 +196,73 @@ In-place editor inputs and header/footer filters receive an accessible name deri 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. -## Selection modes +## Screen reader support -The keyboard behavior of the body depends on which selection system is enabled. The two are independent and drive different ARIA output and shortcut semantics. +DHTMLX Grid is tested against the most widely used assistive technologies: -### 1. Cell / row selection — selection +| Screen reader | Browser | Platform | +| ------------- | ------- | -------- | +| NVDA | Firefox | Windows | +| JAWS | Chrome | Windows | +| VoiceOver | Safari | macOS | -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. +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: + +| State | Exposed through | Read on | +| ------ | --------------- | ------- | +| Selection | `aria-selected` | The focused cell, or the row in `selection: "row"` mode | +| Editability | `aria-readonly` | The grid container and each cell | +| Sort direction | `aria-sort` (`none` / `ascending` / `descending`) | The column header | +| Hierarchy | `aria-level`, `aria-expanded` | Tree rows (`type: "tree"`) | +| Multi-selection capability | `aria-multiselectable` | The 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()`](grid/api/grid_announce_method.md) method: ~~~jsx -// 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 -}); +grid.announce("5 rows imported"); ~~~ -| `selection` value | Meaning | -| --- | --- | -| `"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 | -| `true` | Equivalent to cell selection | -| *falsy / unset* | Selection (and `aria-selected`) disabled | +The message is always **polite**: the screen reader finishes its current sentence first, so an announcement never interrupts the user. -### 2. Range / block selection — blockSelection +The Grid already announces the following out of the box: -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. +| Event | Announced text | +| ----- | -------------- | +| Sorting by a column | `Sorted by {column}, ascending` / `Sorted by {column}, descending` | +| Applying a filter | `{count} rows match the filter` | +| Clearing the filter | `Filter cleared, {count} rows` | +| Loading data | `{count} rows loaded` | +| Entering an out-of-range number in an editor | `Value must be between {min} and {max}` and, once it is fixed, `Value corrected to {value}` | + +### Translatable screen-reader names + +The strings the screen reader hears live in the Grid locale, so they translate 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_enterGrid`, `aria_exitGrid`, `aria_rowId`, and the drag-panel names. + +Override them like any other locale label, before the Grid is initialized: ~~~jsx -// 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 +dhx.i18n.setLocale("grid", { + aria_sortBy: "Sortieren nach {column}", + aria_rowsLoaded: "{count} Zeilen geladen" }); -~~~ -| `blockSelection` value | Mode | Keyboard effect | -| ---------------------- | ---- | --------------- | -| `true` | range | Arrows move the range; Shift+arrows extend the rectangle; Delete clears it | +const grid = new dhx.Grid("grid_container", config); +~~~ -Both systems coexist with the same navigation keys; the Grid responds to whichever selection system is active. +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](grid/localization.md) article for the full locale workflow. ## Keyboard navigation -Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. +Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. Moving the selection with the arrow keys additionally requires one of the selection modules — see [Setting up an accessible Grid](#recommendedconfiguration). Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. Shortcuts are organized into **zones** — body, header, footer — and resolved by where focus currently is. The full reference is in the [Keyboard navigation](grid/configuration.md#keyboard-navigation) article; the tables below summarize it. @@ -203,74 +314,23 @@ Shortcuts are organized into **zones** — body, header, footer — and resolved 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. ::: -## Assistive technology - -DHTMLX Grid is tested against the most widely used assistive technologies: - -| Screen reader | Browser | Platform | -| ------------- | ------- | -------- | -| NVDA | Firefox | Windows | -| JAWS | Chrome | Windows | -| VoiceOver | Safari | macOS | +## Low vision and colour -What the Grid exposes to them is driven entirely by the ARIA markup above — there is no separate announcement layer to configure. +### High-contrast themes {#highcontrastthemes} -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. +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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. -The rest of the state travels the same way, on the element it belongs to: + -| State | Exposed through | Read on | -| ------ | --------------- | ------- | -| Selection | `aria-selected` | The focused cell, or the row in `selection: "row"` mode | -| Editability | `aria-readonly` | The grid container and each cell | -| Sort direction | `aria-sort` (`none` / `ascending` / `descending`) | The column header | -| Hierarchy | `aria-level`, `aria-expanded` | Tree rows (`type: "tree"`) | -| Multi-selection capability | `aria-multiselectable` | The grid container | +### Other low-vision support -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. - -## High contrast and focus - -- **High-contrast themes.** Light and dark high-contrast themes ship with the library (`contrast-light` and `contrast-dark`), activated by setting `data-dhx-theme="contrast-light"` or `data-dhx-theme="contrast-dark"`. Both meet WCAG AA contrast and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. - - -- **Visible focus.** Focus is tracked per zone by the roving-tabindex model, so the active cell is the single tab stop and moves predictably with the arrow keys. The active cell is marked by a persistent 2px solid border in the theme primary color; header and footer cells show a 2px focus ring, and filter inputs and open editors an inset ring. Because keyboard navigation moves the selection, the focus position stays visible throughout arrow-key navigation. +- **Colour 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 the active cell is the single tab stop and moves predictably with the arrow keys. The active cell is marked by a persistent 2px solid border in the theme primary colour; 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 height, so the focused cell is never left behind a frozen zone. -## Configuration recipes - -~~~jsx -// A. Cell navigation (single active cell) -new dhx.Grid("grid_container", { - columns, data, - selection: "complex", - multiselection: true, // Shift+Arrow multi-select - keyNavigation: true, // default - sortable: true // default — keyboard sort in headers -}); - -// B. Spreadsheet-style range selection -new dhx.Grid("grid_container", { - columns, data, - blockSelection: true, // range mode: Shift+Arrow grows the rectangle, Delete clears - editable: true -}); - -// C. TreeGrid (adds role="treegrid", aria-level, aria-expanded, -// and arrow-key expand/collapse) -new dhx.Grid("grid_container", { - columns, data, - type: "tree", - selection: "complex" -}); - -// WAI-ARIA semantics are always emitted — there is no flag to toggle them. -~~~ - - - ## Host-page responsibilities A few accessibility requirements live at the page level, not inside the component. Make sure the host document: @@ -295,7 +355,10 @@ Accessibility is validated continuously, and against the component source rather - [keyNavigation](grid/api/grid_keynavigation_config.md) - [selection](grid/api/grid_selection_config.md) - [blockSelection](grid/api/grid_blockselection_config.md) +- [announce()](grid/api/grid_announce_method.md) +- [Localization](grid/localization.md) - [TreeGrid mode](grid/treegrid_mode.md) +- [Themes overview](/themes/) - [Accessibility support across DHTMLX Suite](common_features/accessibility_support.md) - [WCAG 2.2](https://www.w3.org/TR/WCAG22/) - [WAI-ARIA Authoring Practices: Grid / Treegrid](https://www.w3.org/WAI/ARIA/apg/patterns/) diff --git a/docs/grid/accessibility_conformance_report.md b/docs/grid/accessibility_conformance_report.md index 8a6d6a28c..e54d25fa1 100644 --- a/docs/grid/accessibility_conformance_report.md +++ b/docs/grid/accessibility_conformance_report.md @@ -18,7 +18,7 @@ This report documents how DHTMLX Grid conforms to the major accessibility standa ## Notes -- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output active (always on - there is no flag to disable it), and an AA-conformant colour scheme selected through `data-dhx-theme="contrast-light"` or `data-dhx-theme="contrast-dark"`. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. +- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), one of the selection modules enabled (`selection` or `blockSelection`) so that the keyboard has an active cell to move, WAI-ARIA output active (enabled by default), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. - DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities - the page title, ``, skip/bypass mechanisms, and site-wide navigation - belong to the host application and are reported as *Not Applicable* with an explanation. - In the WCAG tables, only the **Web** response is populated. The *Electronic Docs*, *Software*, *Closed*, and *Authoring Tool* rows are *Not Applicable*: the product is not standalone (non-web) software, closed functionality, an authoring tool, or a piece of electronic documentation. @@ -48,7 +48,8 @@ they are part of WCAG 2.2 and are referenced by EN 301 549.* ## WCAG 2.x report DHTMLX Grid is web content, so only the **Web** response is populated below. "Requires an AA-conformant -theme" and "requires `keyNavigation`, on by default" are configuration dependencies, not defects. +theme", "requires `keyNavigation`, on by default" and "requires a selection module (`selection` or +`blockSelection`)" are configuration dependencies, not defects. ### Table 1: Success Criteria, Level A @@ -61,9 +62,9 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **1.3.1** Info and Relationships | Supports | The grid uses `grid` → `rowgroup` → `row` → `gridcell`/`columnheader` roles, and `treegrid` in tree mode. Position is exposed with `aria-rowindex`/`aria-colindex` against `aria-rowcount`/`aria-colcount`; editability with `aria-readonly`; multi-selection capability with `aria-multiselectable`; sort state with `aria-sort`; hierarchy with `aria-level` and `aria-expanded`. ARIA output is always on. | | **1.3.2** Meaningful Sequence | Supports | DOM and reading order follow the visual order. Rows and columns are virtualized, but `aria-rowindex`/`aria-colindex` carry the absolute position in the dataset rather than the position in the DOM, so sequence is preserved for assistive technology. | | **1.3.3** Sensory Characteristics | Supports | No instruction or label relies solely on shape, size, or spatial location; interactive elements carry text or an ARIA name. | -| **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. | +| **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. The `contrast-light` and `contrast-dark` themes are additionally colour-blind friendly; verified under protanopia / deuteranopia / tritanopia / achromatopsia emulation. | | **1.4.2** Audio Control | Not Applicable | The product produces no auto-playing audio. | -| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`). The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End` and `Page Up`/`Page Down` move the selection; `Shift` combinations extend it; `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | +| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`); moving the selection with the arrow keys additionally requires one of the selection modules (`selection` or `blockSelection`), as described in the Accessibility guide. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End` and `Page Up`/`Page Down` move the selection; `Shift` combinations extend it; `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | | **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. Each zone is entered and left with `Tab`/`Shift`+`Tab` through hidden focus sentinels, and `Escape` deactivates a header or footer filter control and returns focus to its cell. | | **2.1.4** Character Key Shortcuts | Supports | No shortcut uses a bare letter, digit or punctuation key. The only letter-based bindings are `Ctrl`+`Z` and `Ctrl`+`Shift`+`Z`; everything else uses arrows, `Enter`, `Escape`, `Tab`, `F2`, `Space`, `Delete`, `Home`/`End` or `Page Up`/`Page Down`. Handlers are additionally gated on the grid holding focus. | | **2.2.1** Timing Adjustable | Supports | The product sets no time limits: no session timeout, no auto-refresh and no polling. The header filter's 500 ms debounce delays applying a filter, not the user's input. | @@ -95,7 +96,7 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **1.2.5** Audio Description (Prerecorded) | Not Applicable | No prerecorded video. | | **1.3.4** Orientation | Supports | Content is not restricted to a single display orientation. | | **1.3.5** Identify Input Purpose | Not Applicable | The product's editors collect application data defined by the integrator's columns, not the user's own personal information covered by the WCAG input-purpose list. | -| **1.4.3** Contrast (Minimum) | Supports | The `contrast-light` and `contrast-dark` themes meet AA out of the box: body text is about 8.5:1 and 13.2:1 against the cell background, secondary and additional text about 7.3:1 and 10.2:1, and both themes raise the base font size to 16px. The built-in dark theme also meets AA for body text. Meeting the criterion requires selecting one of these themes - one supported method meets it. | +| **1.4.3** Contrast (Minimum) | Supports | The `contrast-light` and `contrast-dark` themes meet AA out of the box: body text is about 8.5:1 and 13.2:1 against the cell background, secondary and additional text about 7.3:1 and 10.2:1, and both themes raise the base font size to 16px and are colour-blind friendly. The built-in dark theme also meets AA for body text. Meeting the criterion requires selecting one of these themes - one supported method meets it. | | **1.4.4** Resize Text | Supports | Text can be resized to 200% without loss of content or functionality; the header, body and footer scale with it and the grid does not break. Typography and row geometry are token-driven (`--dhx-font-size-normal`, `--dhx-line-height-normal`, `--dhx-step-size`, `rowHeight`), and the contrast themes ship a 16px base. | | **1.4.5** Images of Text | Supports | All text is rendered as real text. Icons are font glyphs; the product uses no raster images. | | **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. As a data grid, two-dimensional layout is intrinsic to the content, for which the criterion permits two-dimensional scrolling. | @@ -114,7 +115,7 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **3.3.3** Error Suggestion | Supports | Where a correction is known, it is applied: numeric input outside the column's `min`/`max` bounds is corrected to the nearest valid value, so a valid value is always produced. Suggestion text for application-level validation is provided by the host application, through `beforeEditEnd`. | | **3.3.4** Error Prevention (Legal, Financial, Data) | Supports | Actions that change or remove data are reversible and guarded: the History module provides undo and redo for edits, range clearing and clipboard operations, and every such action can additionally be gated by the host through the cancelable `before*` events - among them `beforeEditEnd`, `beforeKeyDown`, `beforeSort`, `beforeResizeStart`, `beforeRowDrag`/`beforeRowDrop` and `beforeColumnDrag`/`beforeColumnDrop`. | | **3.3.8** Accessible Authentication (Minimum) (2.2 only) | Not Applicable | The product performs no authentication. | -| **4.1.3** Status Messages | Supports | A visually-hidden `aria-live` region announces dynamic changes without moving focus - sort direction changes, filter results and completed loads. State that persists is additionally exposed on the elements themselves: sort direction through `aria-sort`, selection through `aria-selected`, dataset size through `aria-rowcount`/`aria-colcount`, and branch state through `aria-expanded`. | +| **4.1.3** Status Messages | Supports | A visually-hidden polite live region (`role="status"`, `aria-live="polite"`, `aria-atomic="true"`) announces dynamic changes without moving focus - sort direction changes, filter results with the matching row count, completed loads, and numeric-editor range corrections. The same region is available to the host application through the public [`announce()`](grid/api/grid_announce_method.md) method, and every built-in message is localizable through the `aria_*` locale keys. State that persists is additionally exposed on the elements themselves: sort direction through `aria-sort`, selection through `aria-selected`, dataset size through `aria-rowcount`/`aria-colcount`, and branch state through `aria-expanded`. | ## Revised Section 508 report @@ -127,7 +128,7 @@ is Not Applicable - DHTMLX Grid is a software component with no hardware or phys |---|---|---| | 302.1 Without Vision | Supports | Operable with a screen reader through the `grid`/`treegrid` model, the position and state attributes, the accessible names on editors and filters, and the keyboard model. See WCAG 1.3.1, 2.1.1, 4.1.2. | | 302.2 With Limited Vision | Supports | Zoom, text resize and reflow are supported, and AA contrast is met by the `contrast-light` / `contrast-dark` themes, which also raise the base font size to 16px. See WCAG 1.4.3, 1.4.4, 1.4.10, 1.4.11. | -| 302.3 Without Perception of Color | Supports | No information is conveyed by colour alone: sort direction, selection, editability and hierarchy each carry a glyph or an ARIA state alongside the colour. See WCAG 1.4.1. | +| 302.3 Without Perception of Color | Supports | No information is conveyed by colour alone: sort direction, selection, editability and hierarchy each carry a glyph or an ARIA state alongside the colour, and the `contrast-light` / `contrast-dark` themes are colour-blind friendly. See WCAG 1.4.1. | | 302.4 Without Hearing | Supports | No information is conveyed by sound. | | 302.5 With Limited Hearing | Supports | No information is conveyed by sound. | | 302.6 Without Speech | Supports | No speech input is required to operate the product. | @@ -148,7 +149,7 @@ is Not Applicable - DHTMLX Grid is a software component with no hardware or phys | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, the selection modes, the high-contrast themes and the focus model, together with the related configuration. | +| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, the selection modules, the high-contrast themes and the focus model, together with the related configuration, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | | 602.3 Electronic Support Documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA. | | 602.4 Alternate Formats for Non-Electronic Support Documentation | Not Applicable | Documentation is delivered electronically only; there are no non-electronic (print) support documents. | | 603.2 Information on Accessibility and Compatibility Features | Supports | The product's accessibility features are documented publicly in the [Accessibility](grid/accessibility.md) guide and can be provided by the support channel on request. | @@ -167,7 +168,7 @@ These mirror the Section 508 Chapter 3 results (same conclusions). |---|---|---| | 4.2.1 Usage without vision | Supports | See 508 302.1 (screen-reader operable through the grid/treegrid model, position and state attributes, and the keyboard model). | | 4.2.2 Usage with limited vision | Supports | See 508 302.2 (AA contrast and a 16px base through the contrast themes; zoom and reflow supported). | -| 4.2.3 Usage without perception of colour | Supports | See 508 302.3 (glyphs and ARIA state accompany every colour cue). | +| 4.2.3 Usage without perception of colour | Supports | See 508 302.3 (glyphs and ARIA state accompany every colour cue; the contrast themes are colour-blind friendly). | | 4.2.4 Usage without hearing | Supports | No information conveyed by sound. | | 4.2.5 Usage with limited hearing | Supports | No information conveyed by sound. | | 4.2.6 Usage with no or limited vocal capability | Supports | No speech input required. | @@ -200,7 +201,7 @@ it is not non-web software. The WCAG-mapped software provisions (11.1.1.1-11.4.1 | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, the selection modes, the high-contrast themes and the focus model. | +| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, the selection modules, the high-contrast themes, the focus model, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | | 12.1.2 Accessible documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA (see 508 602.3). | | 12.2.2 Information on accessibility and compatibility features | Supports | Accessibility features are documented publicly and can be surfaced by support on request. | | 12.2.3 Effective communication | Supports | Support is provided over a text-based email channel; users interact through their own assistive technology. | From e7e68ff74d623c2a05daf48b742440e59a302d27 Mon Sep 17 00:00:00 2001 From: osorokin Date: Wed, 23 Sep 2026 16:28:18 +0300 Subject: [PATCH 03/10] [dev] Docs accessibility updates (DHX-5290) --- docs/common_features/accessibility_support.md | 125 ++++++++++++++++-- docs/grid/accessibility.md | 113 +++++++++------- docs/grid/accessibility_conformance_report.md | 27 ++-- docs/grid/api/grid_keynavigation_config.md | 6 +- docs/grid/configuration.md | 117 ++++++++-------- 5 files changed, 257 insertions(+), 131 deletions(-) diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index a2b540b39..2c4d5e56e 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -16,14 +16,15 @@ Currently the most commonly encountered conformance guidelines and standards are - [ADA](https://www.ada.gov/) - US Department of Justice - [Section 508](https://www.section508.gov/) - US federal agencies -- [WCAG 2.0](https://www.w3.org/WAI/standards-guidelines/wcag/) - globally accepted standard. WCAG 2.0 has 3 levels of conformance; A, AA and AAA (in order of conformance) +- [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) - globally accepted standard. WCAG has 3 levels of conformance; A, AA and AAA (in order of conformance). The current version is WCAG 2.2 - [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) - a standard that defines ways to make web content and web applications more accessible to people with disabilities - [WAI](https://www.w3.org/WAI/) - web accessibility standard -The DHTMLX Suite library meets the main requirements of the WCAG 2.0 standards and provides support for the following accessibility components: +The DHTMLX Suite library meets the main requirements of the WCAG standards and provides support for the following accessibility components: - [WAI-ARIA Attributes](#wai-aria-attributes) - [Keyboard Navigation](#keyboard-navigation) +- [Localization of screen-reader strings](#localization-of-screen-reader-strings) - [High-Сontrast Themes](#high-contrast-themes) ## WAI-ARIA Attributes @@ -36,8 +37,10 @@ There are **roles** and **attributes** for elements of grid, sorting, filters, e You can find the following **roles** and **attributes** in the DOM: -- role: *grid* (or *treegrid* in the `type: "tree"` mode), *rowgroup*, *row*, *columnheader*, *gridcell*, *button* -- aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort*, *aria-selected*, *aria-readonly*, *aria-multiselectable*, and — for tree rows — *aria-level* and *aria-expanded*. +- role: *grid* (or *treegrid* in the `type: "tree"` mode), *rowgroup*, *row*, *columnheader*, *gridcell*, *button* (the sort control and the tree expand/collapse toggle) +- aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort* (on sortable columns), *aria-selected*, *aria-readonly*, *aria-multiselectable*, *aria-colspan* and *aria-rowspan* (on merged cells), and — for tree rows — *aria-level* and *aria-expanded*. + +The cell surface is a single tab stop, and the arrow keys move the active cell inside it, whether or not a selection module is enabled. In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. Dynamic changes - sorting, filtering, data loading - are read out through a visually hidden polite live region, which the application can write to itself; all the built-in screen-reader strings are stored in the `aria_*` locale keys and can be translated. @@ -63,32 +66,130 @@ There are general description of the component, items and their states. All the Role presentation and aria-hidden are used to hide redundant markup from the accessibility tree. -### Toolbar +### Toolbar, Menu, Sidebar and Ribbon -There are general description of the component, items and their states. All the **roles** and **attributes** are enabled by default You can find the following **roles** and **attributes** in the DOM: +These widgets are built on the same navigation bar and share one accessibility model. All the **roles** and **attributes** are enabled by default. You can find the following **roles** and **attributes** in the DOM: + +- role: *toolbar* (Toolbar, Ribbon, and Sidebar with the vertical orientation), *menubar* (Menu), *menu*, *menuitem*, *menuitemcheckbox*, *menuitemradio*, *button*, *combobox* +- aria attributes: *label*, *orientation*, *haspopup*, *expanded*, *disabled*, *pressed*, *checked*. + +A few details worth knowing: -- role: *toolbar*, *button*, *combobox*, *menu*, *menuitem* -- aria attributes: *label*, *labeledby*, *orientation*, *haspopup*, *expanded*, *disabled*, *pressed*. +- a button reports its pressed state (`aria-pressed`) in both positions when the item is declared as a toggle, with `twoState: true`, or as a member of a set, with `group`. Inside a menu, such items are exposed as `menuitemcheckbox` / `menuitemradio` with `aria-checked`; +- a menu is named after the text (or the tooltip) of the control that opens it; +- a button built from custom HTML takes its accessible name from its `tooltip`, so give icon-only custom buttons a tooltip; +- the toolbar is not a navigation landmark. + +The accessible name of a Toolbar is set with the `ariaLabel` configuration option. Set it whenever a page has more than one toolbar, so that users can tell them apart. Without it, the name falls back to the `aria_toolbar` locale string (`"Toolbar"`): + +~~~jsx +const formatting = new dhx.Toolbar("formatting", { + ariaLabel: "Formatting", + data: formattingItems +}); +~~~ Role presentation and aria-hidden are used to hide redundant markup from the accessibility tree. +### Window + +A window is exposed with `role="dialog"`. Its accessible name is taken from the `ariaLabel` configuration option, then from `title`, and, when there is neither, from the `aria_dialog` locale string (`"Dialog"`). Use `ariaLabel` for a window without a visible title: + +~~~jsx +const search = new dhx.Window({ + ariaLabel: "Search", + width: 405, + height: 56 +}); +~~~ + +`aria-modal="true"` is set only on a modal window (`modal: true`). The window container is focusable from script, but it is not a stop in the tab order of the page, so opening a window does not change the tab order of the host page. + +### Colorpicker + +The color palette is exposed as a list of options. You can find the following **roles** and **attributes** in the DOM: + +- role: *listbox* (the palette), *option* (each swatch and the "Add new color" control), *group* (the custom colors) +- aria attributes: *label*, *labelledby*, *selected*. + +Each swatch is named by its color name, for example "dark green", rather than by its HEX value, and the selected swatch reports `aria-selected="true"`. + +### Message + +The buttons of alert and confirmation dialogs take their accessible names from the `message` locale: `aria_confirm`, `aria_reject` and `aria_apply`. + ## Keyboard Navigation All DHTMLX Suite widgets are provided with a keyboard navigation support. It allows using a Suite-based app without a mouse pointer. Basic rules include: -- the Tab key is used to navigate between widgets and clickable areas of the widgets -- the Esc key closes windows and editors -- the Enter is used to open and hide drop-down lists of select controls +- the Tab key is used to navigate between widgets and clickable areas of the widgets. A group of controls - a grid, a toolbar, a menu bar, a sidebar, a ribbon, a color palette - is a single tab stop - the Arrow keys are used to move selection or change active elements within widgets +- the Esc key closes windows, menus and editors +- the Enter is used to open and hide drop-down lists of select controls + +The focus indicator stays visible during the whole keyboard interaction, including arrow-key navigation, and is removed when the user switches to a pointer. + +### Toolbar, Menu, Sidebar and Ribbon + +| Keys | Action | +| ---- | ------ | +| Tab / Shift + Tab | Enter or leave the bar. Returning to the bar puts focus on the control that was used last | +| ← / → (Toolbar, Menu, Ribbon), ↑ / ↓ (Sidebar) | Move between the controls of the bar, wrapping at both ends. Separators, spacers and disabled controls are skipped | +| Home / End | Move to the first / last control | +| ↓ (Toolbar, Menu, Ribbon), → (Sidebar), Enter / Space | Open the menu of the focused control and move focus into it | + +A text field inside a bar keeps the arrow keys while the caret can move, and passes them to the bar when the caret is at the matching end of the text and nothing is selected. + +Inside an open menu: + +| Keys | Action | +| ---- | ------ | +| ↑ / ↓ | Move between the menu entries, wrapping at both ends | +| Home / End | Move to the first / last entry | +| → | Open a submenu and move focus into it | +| ← | Go one level back: to the entry that opened the submenu, or to the control of the bar | +| Enter / Space | Activate the entry, close the menu and return focus to the control that opened it | +| Esc | Close the menu and return focus to the control that opened it | +| Tab | Close the menu and leave the bar | + +### Colorpicker + +| Keys | Action | +| ---- | ------ | +| Tab | Enter the palette on the selected color, or on the first swatch when nothing is selected | +| ↑ / ↓ / ← / → | Move between swatches in the order they are shown on the screen. Movement stops at the edges of the palette | +| Home / End | Move to the first / last swatch | +| Enter | Select the focused color | + +Focus moves into the custom color view when it opens, and back to the palette when the view is closed. :::info For the full list of built-in hotkeys, refer to the **Keyboard Navigation** articles of the following widgets: -- [Grid](grid/configuration.md#keyboard-navigation) +- [Grid](grid/configuration.md#keyboard-navigation) and the [Grid accessibility](grid/accessibility.md#keyboard-navigation) guide - [List](list/configuration.md#arrow-keys-navigation) - [Tree](tree/configuration.md#key-navigation) - [DataView](dataview/configuration.md#arrow-keys-navigation) ::: +## Localization of screen-reader strings + +The accessible names and announcements that the widgets generate are stored in the locale of each widget as keys with the `aria_` prefix, for example `aria_toolbar` in the `toolbar` locale, `aria_dialog` in the `window` locale, `aria_palette` and the color names in the `colorpicker` locale, and `aria_confirm` in the `message` locale. Translate them with the `dhx.i18n.setLocale()` method before the widget is initialized: + +~~~jsx +dhx.i18n.setLocale("toolbar", { + aria_toolbar: "Symbolleiste" +}); +dhx.i18n.setLocale("window", { + aria_dialog: "Dialogfeld" +}); +dhx.i18n.setLocale("colorpicker", { + aria_palette: "Farbpalette", + aria_colorDarkGray: "dunkelgrau" +}); +~~~ + +The locale keys are flat: pass each key at the top level of the object, not inside a nested object. + ## High-Contrast Themes For users that are visually impaired due to color deficiencies, care should be taken when using colors to provide information. diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index 382f49c25..6a89c52a4 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -18,10 +18,10 @@ Conformance is reported criterion by criterion rather than as a blanket claim. T | Area | Support | | ---- | ------- | -| Keyboard operation | Full: cell navigation, editing, sorting, range selection, tree expand/collapse, and clipboard all have keyboard equivalents | +| Keyboard operation | Full: 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 semantics | Built-in (`grid` / `treegrid` model), enabled by default | -| Focus model | A single tab stop per zone; focus moves between header, body, and footer | -| Selection model | Two modes: single-cell/row (`selection`) and spreadsheet-style range (`blockSelection`) | +| Focus model | A single tab stop for the whole cell surface; focus moves between header, body, and footer | +| Selection model | Optional: single-cell/row (`selection`) or spreadsheet-style range (`blockSelection`), for selecting cells and ranges | | Screen readers | Tested with NVDA, JAWS and VoiceOver | | Visual accessibility | Dedicated light and dark high-contrast themes — colour-blind friendly, AA contrast, 16px base — and non-colour cues | | Text resize / zoom | Layout remains operable up to 400% zoom / text spacing overrides | @@ -47,31 +47,47 @@ As with any embeddable component, the accessibility of the final page also depen ## Setting up an accessible Grid {#recommendedconfiguration} -The Grid ships accessible by default — WAI-ARIA output is emitted automatically and keyboard navigation is on ([`keyNavigation: true`](grid/api/grid_keynavigation_config.md)) — but a fully WCAG 2.2 AA-compliant deployment needs two more things: one of the selection modules enabled, so that keyboard navigation has an active cell to move and to expose through `aria-selected`, and an AA-contrast theme selected. The recommended configuration is: +The Grid ships accessible by default: WAI-ARIA output is emitted automatically, and keyboard navigation is on ([`keyNavigation: true`](grid/api/grid_keynavigation_config.md)), 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: ~~~jsx -// 1. WAI-ARIA output is enabled by default - no action needed. - -// 2. Enable a selection module so keyboard navigation has an active cell to move +// 1. WAI-ARIA output and keyboard navigation are enabled by default - no action needed const grid = new dhx.Grid("grid_container", { columns: [/* ... */], data: dataset, - selection: "complex", // or blockSelection: true for spreadsheet-style ranges - multiselection: true, // enables Shift+Arrow multi-select - keyNavigation: true // true - by default + keyNavigation: true // true - by default }); -// 3. Select an AA-conformant theme +// 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](/themes/) for all the options. -### Selection modules for keyboard operation {#selectionmodules} +Add a selection module only when users need to select cells or ranges; see the next section. + +### Keyboard navigation with and without selection {#selectionmodules} + +#### 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: + +~~~jsx +// 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 -The keyboard behavior of the body depends on which selection system is enabled. The two are independent and drive different ARIA output and shortcut semantics. +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 +##### 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. @@ -93,9 +109,9 @@ const grid = new dhx.Grid("grid_container", { | `"row"` | One active row; arrows move the whole row, `aria-selected` is on the row | | `"complex"` | Cell- and row-style selection combined | | `true` | Equivalent to cell selection | -| *falsy / unset* | Selection (and `aria-selected`) disabled | +| *falsy / unset* | Selection (and `aria-selected`) disabled; keyboard navigation still works | -#### 2. Range / block selection — blockSelection +##### 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. @@ -114,11 +130,11 @@ const grid = new dhx.Grid("grid_container", { | ---------------------- | ---- | --------------- | | `true` | range | Arrows move the range; Shift+arrows extend the rectangle; Delete clears it | -Both systems coexist with the same navigation keys; the Grid responds to whichever selection system is active. +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` on top of the selection module, and enables arrow-key expand/collapse: +TreeGrid mode (`type: "tree"`) adds `role="treegrid"`, `aria-level` and `aria-expanded`, and enables arrow-key expand/collapse: ~~~jsx const grid = new dhx.Grid("grid_container", { @@ -140,16 +156,16 @@ WAI-ARIA roles and attributes are added to the component markup automatically an ### Grid container -The following table lists the container role and the grid-wide attributes applied to the root grid element, which describe the grid as a whole: +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: | Selector | Role / attribute | Purpose | | -------- | ---------------- | ------- | -| `.dhx_grid` | `role="grid"` | Standard grid | -| `.dhx_grid` | `role="treegrid"` | TreeGrid (`type: "tree"`) | -| `.dhx_grid` | `aria-rowcount` | Total number of data rows | -| `.dhx_grid` | `aria-colcount` | Number of visible columns | -| `.dhx_grid` | `aria-readonly` | `"true"` when the grid is not editable | -| `.dhx_grid` | `aria-multiselectable` | `"true"` when multi-selection is enabled | +| `.dhx_grid-content` | `role="grid"` | Standard grid | +| `.dhx_grid-content` | `role="treegrid"` | TreeGrid (`type: "tree"`) | +| `.dhx_grid-content` | `aria-rowcount` | Total number of data rows | +| `.dhx_grid-content` | `aria-colcount` | Number of visible columns | +| `.dhx_grid-content` | `aria-readonly` | `"true"` when the grid is not editable | +| `.dhx_grid-content` | `aria-multiselectable` | `"true"` when multi-selection is enabled | ### Rows and cells @@ -162,7 +178,9 @@ The following table lists the roles and the position and state attributes applie | Data cell | `role="gridcell"` | A data cell | | Data cell | `aria-colindex` | 1-based column position | | Data cell | `aria-readonly` | `"true"` when the cell is not editable | -| Data cell | `aria-selected` | Selection state of the cell | +| Data cell | `aria-selected` | Selection state of the cell, when a selection module is enabled | +| Data cell | `tabindex` | `"0"` on exactly one cell (the active cell), `"-1"` on the rest, so the cell surface is a single tab stop | +| Merged cell | `aria-colspan` / `aria-rowspan` | Number of columns / rows the merged cell covers | ### Tree (TreeGrid) rows @@ -183,7 +201,7 @@ The following table lists the roles and attributes applied to the header and foo | -------- | ---------------- | ------- | | Header/footer group | `role="rowgroup"` | Groups the header or footer rows | | Header/footer row | `role="row"` + `aria-rowindex` | A header or footer row | -| Header cell | `role="columnheader"` + `aria-sort` | Column header; `aria-sort` is `none` / `ascending` / `descending` | +| Header cell | `role="columnheader"` + `aria-sort` | Column header; `aria-sort` is `none` / `ascending` / `descending` and is present only on sortable columns | | Footer cell | `role="gridcell"` + `aria-colindex` | A footer (summary) cell | | Content (filter) cell | `role="gridcell"` | Header/footer cell hosting a filter control | | Sort affordance | `role="button"` + `aria-label="Sort by …"` | Keyboard- and pointer-activatable sort trigger | @@ -194,7 +212,7 @@ In-place editor inputs and header/footer filters receive an accessible name deri ### 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. +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 @@ -212,10 +230,11 @@ The rest of the state travels the same way, on the element it belongs to: | State | Exposed through | Read on | | ------ | --------------- | ------- | -| Selection | `aria-selected` | The focused cell, or the row in `selection: "row"` mode | +| Selection | `aria-selected` | The selected cell, or the row in `selection: "row"` mode, when a selection module is enabled | | Editability | `aria-readonly` | The grid container and each cell | -| Sort direction | `aria-sort` (`none` / `ascending` / `descending`) | The column header | +| Sort direction | `aria-sort` (`none` / `ascending` / `descending`) | The header of a sortable column | | Hierarchy | `aria-level`, `aria-expanded` | Tree rows (`type: "tree"`) | +| Merged cells | `aria-colspan`, `aria-rowspan` | The merged cell | | Multi-selection capability | `aria-multiselectable` | The 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. @@ -245,7 +264,7 @@ The Grid already announces the following out of the box: The strings the screen reader hears live in the Grid locale, so they translate 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_enterGrid`, `aria_exitGrid`, `aria_rowId`, and the drag-panel names. +- **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: @@ -262,29 +281,29 @@ Placeholders in curly braces (`{column}`, `{count}`, `{min}`, `{max}`, `{value}` ## Keyboard navigation -Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. Moving the selection with the arrow keys additionally requires one of the selection modules — see [Setting up an accessible Grid](#recommendedconfiguration). Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. +Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. It works in every configuration: without a selection module the keys move the active cell, and with one they move the selection as well — see [Keyboard navigation with and without selection](#selectionmodules). Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. Tab at the first or the last cell leaves the Grid in every configuration, including one with frozen columns (`leftSplit` / `rightSplit`). Shortcuts are organized into **zones** — body, header, footer — and resolved by where focus currently is. The full reference is in the [Keyboard navigation](grid/configuration.md#keyboard-navigation) article; the tables below summarize it. ### Grid body -| Keys | Action | Selection mode | -| ---- | ------ | -------------- | -| ↑ / ↓ / ← / → | Move the selected cell one row/column (↑ from the first row enters the header; ↓ from the last row enters the footer when a footer exists) | both | -| Ctrl + arrow | Jump the selection to the first/last cell in that direction | both | +| Keys | Action | Requires | +| ---- | ------ | -------- | +| ↑ / ↓ / ← / → | 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 + arrow | Jump the active cell to the first/last cell in that direction | — | | Shift + arrow | Extend the selection by one cell | `selection` with `multiselection: true`, or `blockSelection` range | | Ctrl + Shift + arrow | Extend the selection to the edge | as above | -| Home / End | Move to the first / last column of the current row | both | -| Ctrl + Home / Ctrl + End | Move to the first / last cell of the grid | both | +| Home / End | Move to the first / last column of the current row | — | +| Ctrl + Home / Ctrl + End | Move to the first / last cell of the grid | — | | Shift + Home / End, Ctrl + Shift + Home / End | Extend the selection to the row/grid extent | extend-capable modes | -| Page Up / Page Down | Move the selected cell up / down by one page of visible rows | both | +| Page Up / Page Down | Move the active cell up / down by one page of visible rows | — | | Shift + Page Up / Page Down | Extend the selection by one page | extend-capable modes | -| Enter | Open the editor (or toggle a boolean cell); when editing, commit and close | requires `editable` | -| F2 | Open the editor of the selected cell (non-boolean) | requires `editable` | -| Space | Toggle a boolean cell | requires `editable` | -| Escape | Cancel editing without saving | requires `editable` | -| Tab / Shift + Tab | Move to the next / previous cell, wrapping rows; exits to the footer / header at the ends | both | -| Delete | Clear the selected range | `blockSelection` range mode + `editable` | +| Enter | Open the editor (or toggle a boolean cell); when editing, commit and close | `editable` | +| F2 | Open the editor of the active cell (non-boolean) | `editable` | +| Space | Toggle a boolean cell | `editable` | +| Escape | Cancel editing without saving | `editable` | +| Tab / Shift + Tab | Move to the next / previous cell, wrapping rows; exits to the footer / header at the ends | — | +| Delete | Clear the selected range | `blockSelection` range mode and `editable` | | Ctrl + Z / Ctrl + Shift + Z | Undo / Redo | History module | | Ctrl + Enter | Expand / collapse the row (`type: "tree"`) | TreeGrid | | → / ← (tree column) | Expand / collapse a branch, or move to first child / parent | TreeGrid | @@ -328,8 +347,8 @@ Light and dark high-contrast themes ship with the library — `contrast-light` a - **Colour 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 the active cell is the single tab stop and moves predictably with the arrow keys. The active cell is marked by a persistent 2px solid border in the theme primary colour; 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 height, so the focused cell is never left behind a frozen zone. +- **Visible focus.** Focus is tracked per zone by the roving-tabindex model, so the active cell is the single 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 colour, 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 diff --git a/docs/grid/accessibility_conformance_report.md b/docs/grid/accessibility_conformance_report.md index e54d25fa1..eea060ebf 100644 --- a/docs/grid/accessibility_conformance_report.md +++ b/docs/grid/accessibility_conformance_report.md @@ -8,7 +8,7 @@ description: You can explore the accessibility conformance report (VPAT 2.5) for This report documents how DHTMLX Grid conforms to the major accessibility standards. It follows the **VPAT® 2.5 (International Edition)** structure, so you can map it directly against your own procurement or compliance checklist. For a task-oriented overview of the accessibility features and how to configure them, see the [Accessibility](grid/accessibility.md) guide. -**Name of Product/Version:** DHTMLX Grid v9.3.10 +**Name of Product/Version:** DHTMLX Grid v9.3.12 **Report Date:** September 2026 @@ -18,7 +18,7 @@ This report documents how DHTMLX Grid conforms to the major accessibility standa ## Notes -- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), one of the selection modules enabled (`selection` or `blockSelection`) so that the keyboard has an active cell to move, WAI-ARIA output active (enabled by default), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. +- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output active (enabled by default), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. - DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities - the page title, ``, skip/bypass mechanisms, and site-wide navigation - belong to the host application and are reported as *Not Applicable* with an explanation. - In the WCAG tables, only the **Web** response is populated. The *Electronic Docs*, *Software*, *Closed*, and *Authoring Tool* rows are *Not Applicable*: the product is not standalone (non-web) software, closed functionality, an authoring tool, or a piece of electronic documentation. @@ -48,8 +48,7 @@ they are part of WCAG 2.2 and are referenced by EN 301 549.* ## WCAG 2.x report DHTMLX Grid is web content, so only the **Web** response is populated below. "Requires an AA-conformant -theme", "requires `keyNavigation`, on by default" and "requires a selection module (`selection` or -`blockSelection`)" are configuration dependencies, not defects. +theme" and "requires `keyNavigation`, on by default" are configuration dependencies, not defects. ### Table 1: Success Criteria, Level A @@ -59,20 +58,20 @@ theme", "requires `keyNavigation`, on by default" and "requires a selection modu | **1.2.1** Audio-only and Video-only (Prerecorded) | Not Applicable | The product contains no audio or video content. | | **1.2.2** Captions (Prerecorded) | Not Applicable | No prerecorded multimedia. | | **1.2.3** Audio Description or Media Alternative (Prerecorded) | Not Applicable | No prerecorded multimedia. | -| **1.3.1** Info and Relationships | Supports | The grid uses `grid` → `rowgroup` → `row` → `gridcell`/`columnheader` roles, and `treegrid` in tree mode. Position is exposed with `aria-rowindex`/`aria-colindex` against `aria-rowcount`/`aria-colcount`; editability with `aria-readonly`; multi-selection capability with `aria-multiselectable`; sort state with `aria-sort`; hierarchy with `aria-level` and `aria-expanded`. ARIA output is always on. | +| **1.3.1** Info and Relationships | Supports | The grid uses `grid` → `rowgroup` → `row` → `gridcell`/`columnheader` roles, and `treegrid` in tree mode; the grid role owns only row groups and rows, and the focus sentinels are placed outside it. Position is exposed with `aria-rowindex`/`aria-colindex` against `aria-rowcount`/`aria-colcount`; the extent of a merged cell with `aria-colspan`/`aria-rowspan`; editability with `aria-readonly`; multi-selection capability with `aria-multiselectable`; sort state with `aria-sort`, on sortable columns only; hierarchy with `aria-level` and `aria-expanded`. ARIA output is always on. | | **1.3.2** Meaningful Sequence | Supports | DOM and reading order follow the visual order. Rows and columns are virtualized, but `aria-rowindex`/`aria-colindex` carry the absolute position in the dataset rather than the position in the DOM, so sequence is preserved for assistive technology. | | **1.3.3** Sensory Characteristics | Supports | No instruction or label relies solely on shape, size, or spatial location; interactive elements carry text or an ARIA name. | | **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. The `contrast-light` and `contrast-dark` themes are additionally colour-blind friendly; verified under protanopia / deuteranopia / tritanopia / achromatopsia emulation. | | **1.4.2** Audio Control | Not Applicable | The product produces no auto-playing audio. | -| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`); moving the selection with the arrow keys additionally requires one of the selection modules (`selection` or `blockSelection`), as described in the Accessibility guide. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End` and `Page Up`/`Page Down` move the selection; `Shift` combinations extend it; `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | -| **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. Each zone is entered and left with `Tab`/`Shift`+`Tab` through hidden focus sentinels, and `Escape` deactivates a header or footer filter control and returns focus to its cell. | +| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`) and works in every configuration, with or without a selection module. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End`, `Page Up`/`Page Down` and `Tab` move the active cell (and the selection, when a selection module is enabled); `Shift` combinations extend the selection; with `editable`, `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | +| **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. Each zone is entered and left with `Tab`/`Shift`+`Tab` through hidden focus sentinels, in every configuration, including one with frozen columns (`leftSplit`/`rightSplit`), and `Escape` deactivates a header or footer filter control and returns focus to its cell. | | **2.1.4** Character Key Shortcuts | Supports | No shortcut uses a bare letter, digit or punctuation key. The only letter-based bindings are `Ctrl`+`Z` and `Ctrl`+`Shift`+`Z`; everything else uses arrows, `Enter`, `Escape`, `Tab`, `F2`, `Space`, `Delete`, `Home`/`End` or `Page Up`/`Page Down`. Handlers are additionally gated on the grid holding focus. | | **2.2.1** Timing Adjustable | Supports | The product sets no time limits: no session timeout, no auto-refresh and no polling. The header filter's 500 ms debounce delays applying a filter, not the user's input. | | **2.2.2** Pause, Stop, Hide | Supports | There is no auto-updating, moving, blinking or scrolling content. The only motion is hover and state transitions of 0.5 s or less. | | **2.3.1** Three Flashes or Below Threshold | Supports | The product contains no flashing content. | | **2.4.1** Bypass Blocks | Not Applicable | Applies at page level; bypass mechanisms belong to the host application. | | **2.4.2** Page Titled | Not Applicable | Page titles are the host application's responsibility. | -| **2.4.3** Focus Order | Supports | A roving-tabindex model gives each zone a single tab stop, and the tab order follows the visual order: header → body → footer → next element on the page. When focus reaches an off-screen (virtualized) row or column, the grid scrolls it into view. | +| **2.4.3** Focus Order | Supports | A roving-tabindex model makes the whole cell surface a single tab stop, in every configuration, with one tab stop per zone, and the tab order follows the visual order: header → body → footer → next element on the page. When focus reaches an off-screen (virtualized) row or column, the grid scrolls it into view. | | **2.4.4** Link Purpose (In Context) | Not Applicable | The widget's own interface contains no links. Links placed in cells through `htmlEnable` or a column template are rendered as authored, so their text and purpose belong to the host application. | | **2.5.1** Pointer Gestures | Supports | No operation requires a multipoint or path-based gesture; touch handling reads a single touch point, and every drag operation has a non-drag alternative (see 2.5.7). | | **2.5.2** Pointer Cancellation | Supports | The primary actions complete on the up-event: sorting, cell and row selection, and tree expand/collapse are all bound to `click`, not to `mousedown`. | @@ -86,7 +85,7 @@ theme", "requires `keyNavigation`, on by default" and "requires a selection modu | **3.3.2** Labels or Instructions | Supports | In-place editors and header/footer filters take an accessible name from the column header text, so the user hears which column is being edited or filtered - for example `Filter ` and `Filter by date: `. | | **3.3.7** Redundant Entry (2.2 only) | Not Applicable | The product contains no multi-step process that re-requests previously entered information. | | **4.1.1** Parsing | Supports | Markup is generated through a virtual-DOM layer and element ids are machine-generated, so tags are well formed and attributes are not duplicated. | -| **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). | +| **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). `aria-sort` is present only on the headers of sortable columns, so no column reports a sort state it cannot have. | ### Table 2: Success Criteria, Level AA @@ -100,13 +99,13 @@ theme", "requires `keyNavigation`, on by default" and "requires a selection modu | **1.4.4** Resize Text | Supports | Text can be resized to 200% without loss of content or functionality; the header, body and footer scale with it and the grid does not break. Typography and row geometry are token-driven (`--dhx-font-size-normal`, `--dhx-line-height-normal`, `--dhx-step-size`, `rowHeight`), and the contrast themes ship a 16px base. | | **1.4.5** Images of Text | Supports | All text is rendered as real text. Icons are font glyphs; the product uses no raster images. | | **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. As a data grid, two-dimensional layout is intrinsic to the content, for which the criterion permits two-dimensional scrolling. | -| **1.4.11** Non-text Contrast | Supports | The active cell is marked by a 2px solid border in the theme primary colour - about 3.7:1 against the default light background and about 6.3:1 on `contrast-dark`, both above the 3:1 requirement. Header and footer cells add a 2px focus ring in the same colour. The contrast themes also raise the cell border contrast. | +| **1.4.11** Non-text Contrast | Supports | With a cell or range selection module, the active cell is marked by a 2px solid border in the theme primary colour - about 3.7:1 against the default light background and about 6.3:1 on `contrast-dark`, both above the 3:1 requirement. Header and footer cells add a 2px focus ring in the same colour. The contrast themes also raise the cell border contrast. | | **1.4.12** Text Spacing | Supports | Applying WCAG text-spacing overrides does not clip or overlap text in grid cells, column headers or footer summaries. | | **1.4.13** Content on Hover or Focus | Supports | Overflow tooltips are hoverable, persistent, and dismissible with `Esc` without moving the pointer. Their content repeats the cell's own text, so no information is available on hover alone, and they can be turned off entirely with `tooltip: false`. | | **2.4.5** Multiple Ways | Not Applicable | Applies to locating pages within a set of web pages; out of scope for a single embedded component (host-application concern). | | **2.4.6** Headings and Labels | Supports | Column headers carry descriptive accessible names, and every filter and in-place editor exposes an accessible name derived from its column. | -| **2.4.7** Focus Visible | Supports | The active cell carries a persistent 2px solid border drawn in the theme primary colour. Because keyboard navigation moves the selection, the keyboard focus location is visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | -| **2.4.11** Focus Not Obscured (Minimum) (2.2 only) | Supports | A focused cell is never left behind a frozen zone: when the grid scrolls a cell into view it compensates for the left, right, top and bottom splits and for the header height. | +| **2.4.7** Focus Visible | Supports | With a cell or range selection module, the active cell carries a persistent 2px solid border drawn in the theme primary colour, and no second outline is drawn over it. With row selection, and in a grid without a selection module, the focused cell carries a dashed focus outline. The keyboard focus location is therefore visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | +| **2.4.11** Focus Not Obscured (Minimum) (2.2 only) | Supports | A focused cell is never left behind a frozen zone: when the grid scrolls a cell into view it compensates for the left, right, top and bottom splits and for the header and footer height. | | **2.5.7** Dragging Movements (2.2 only) | Supports | Every drag operation has a non-drag alternative: range selection through `Shift`+arrows, `Ctrl`+`Shift`+arrows, `Shift`+`Home`/`End` and `Shift`+`Page Up`/`Page Down`; column sizing through `adjust` / `adjustColumnWidth`; and column and row order through the corresponding API. | | **2.5.8** Target Size (Minimum) (2.2 only) | Supports | Interactive targets meet 24x24 CSS px: the sort target is the whole header cell (40px tall by the column width), the boolean cell's hit area is the whole cell, and the column resizer grip and the range fill handle carry an enlarged hit area (visual size unchanged). | | **3.1.2** Language of Parts | Not Applicable | The widget does not mix languages within its content; language is host-owned. | @@ -149,7 +148,7 @@ is Not Applicable - DHTMLX Grid is a software component with no hardware or phys | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, the selection modules, the high-contrast themes and the focus model, together with the related configuration, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | +| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes and the focus model, together with the related configuration, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | | 602.3 Electronic Support Documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA. | | 602.4 Alternate Formats for Non-Electronic Support Documentation | Not Applicable | Documentation is delivered electronically only; there are no non-electronic (print) support documents. | | 603.2 Information on Accessibility and Compatibility Features | Supports | The product's accessibility features are documented publicly in the [Accessibility](grid/accessibility.md) guide and can be provided by the support channel on request. | @@ -201,7 +200,7 @@ it is not non-web software. The WCAG-mapped software provisions (11.1.1.1-11.4.1 | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, the selection modules, the high-contrast themes, the focus model, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | +| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes, the focus model, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | | 12.1.2 Accessible documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA (see 508 602.3). | | 12.2.2 Information on accessibility and compatibility features | Supports | Accessibility features are documented publicly and can be surfaced by support on request. | | 12.2.3 Effective communication | Supports | Support is provided over a text-based email channel; users interact through their own assistive technology. | diff --git a/docs/grid/api/grid_keynavigation_config.md b/docs/grid/api/grid_keynavigation_config.md index 30eca6530..c2e615d29 100644 --- a/docs/grid/api/grid_keynavigation_config.md +++ b/docs/grid/api/grid_keynavigation_config.md @@ -19,7 +19,7 @@ const grid = new dhx.Grid("grid_container", { ], data: dataset, selection: "complex", - editing: true, + editable: true, keyNavigation: false }); @@ -27,8 +27,8 @@ const grid = new dhx.Grid("grid_container", { **Related sample**: [Grid. Key navigation](https://snippet.dhtmlx.com/y9kdk0md) -You need to set the [selection](grid/api/grid_selection_config.md) and [editable](grid/api/grid_editable_config.md) properties in the configuration object of Grid to enable all available shortcut keys. Read the details in the [Key Navigation](grid/configuration.md#keyboard-navigation) article and in the [Grid accessibility](grid/accessibility.md) guide. +Keyboard navigation works without any selection module: the navigation keys move the active cell, but do not select it. Set the [selection](grid/api/grid_selection_config.md) property to move the selection with the keyboard, and set the [editable](grid/api/grid_editable_config.md) property to edit cells from the keyboard. Read the details in the [Key Navigation](grid/configuration.md#keyboard-navigation) article and in the [Grid accessibility](grid/accessibility.md) guide. -@changelog: added in v6.3; the keyboard navigation model was extended in v9.3.5 +@changelog: added in v6.3; the keyboard navigation model was extended in v9.3.5; navigation and editing without a selection module were added in v9.3.12 [comment]: # (@related: grid/initialization.md#initialize-grid grid/configuration.md#keyboard-navigation) diff --git a/docs/grid/configuration.md b/docs/grid/configuration.md index 59a040761..8beb3bda3 100644 --- a/docs/grid/configuration.md +++ b/docs/grid/configuration.md @@ -3166,25 +3166,75 @@ DHTMLX Grid provides the keyboard navigation that will help you manipulate your ### Default shortcut keys +Keyboard navigation is enabled by default and does not require any selection module. The whole cell surface is a single tab stop: Tab enters Grid, the keys below move the active cell and scroll it into view, and Tab leaves Grid at its first or last cell. Without a selection module, moving the active cell does not select it and does not fire selection events. + The navigation shortcut keys and keys combinations that Grid enables by default are provided below: - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3207,9 +3257,11 @@ const grid = new dhx.Grid("grid_container", { **Related sample**: [Grid. Key navigation](https://snippet.dhtmlx.com/y9kdk0md) -### Shortcut keys for moving selection between cells +For the accessibility aspects of keyboard navigation, see the [Grid accessibility](grid/accessibility.md#keyboard-navigation) guide. + +### Shortcut keys for moving selection between cells {#shortcut-keys-for-moving-selection-between-cells} -In case you want to enable the shortcut keys that allow moving the selection between cells, you need to specify the [`selection`](grid/api/grid_selection_config.md) property for Grid. +In case you want the default shortcut keys to move the selection together with the active cell, you need to specify the [`selection`](grid/api/grid_selection_config.md) property for Grid. ~~~jsx {6} const grid = new dhx.Grid("grid_container", { @@ -3224,52 +3276,7 @@ const grid = new dhx.Grid("grid_container", { **Related sample**: [Grid. Key navigation](https://snippet.dhtmlx.com/y9kdk0md) -The list of the shortcut keys and their combinations used for moving selection between cells is the following: - -
PageUpscrolls Grid up to the height of the visible content (without change of the selected cell)ArrowUpmoves the active cell to the previous vertical cell
PageDownscrolls Grid down to the height of the visible content (without change of the selected cell)ArrowDownmoves the active cell to the next vertical cell
ArrowLeftmoves the active cell to the previous horizontal cell
ArrowRightmoves the active cell to the next horizontal cell
Ctrl+ArrowUpmoves the active cell to the first vertical cell
Ctrl+ArrowDownmoves the active cell to the last vertical cell
Ctrl+ArrowLeftmoves the active cell to the first horizontal cell
Ctrl+ArrowRightmoves the active cell to the last horizontal cell
Homenavigates to the beginning of the Grid content (without change of the selected cell)moves the active cell to the first column of the current row
Endnavigates to the end of the Grid content (without change of the selected cell)moves the active cell to the last column of the current row
Ctrl+Homemoves the active cell to the first cell of Grid
Ctrl+Endmoves the active cell to the last cell of Grid
PageUpmoves the active cell up by the height of the visible content
PageDownmoves the active cell down by the height of the visible content
Tabmoves the active cell to the next horizontal cell or the first cell of the next row; from the last cell of Grid, moves the focus out of Grid
Shift+Tabmoves the active cell to the previous horizontal cell or the last cell of the previous row; from the first cell of Grid, moves the focus out of Grid
Ctrl+Enter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArrowUpmoves selection to the previous vertical cell
ArrowDownmoves selection to the next vertical cell
ArrowLeftmoves selection to the previous horizontal cell
ArrowRightmoves selection to the next horizontal cell
Ctrl+ArrowUpmoves selection to the first vertical cell
Ctrl+ArrowDownmoves selection to the last vertical cell
Ctrl+ArrowLeft moves selection to the first horizontal cell
Ctrl+ArrowRight moves selection to the last horizontal cell
Tab moves selection to the next horizontal cell or the first cell of the next row
Shift+Tab moves selection to the previous horizontal cell or to the first cell of the previous row
+With the `selection` property specified, the arrow keys, their combinations with Ctrl, Home/End, PageUp/PageDown and Tab/Shift+Tab listed in the [Default shortcut keys](#default-shortcut-keys) section move the selection between cells. The combinations of the shortcut keys listed below do not work when the `selection` property is set to *"complex"*. Use another mode (*"cell" or "row"*) in case you want to activate these navigation keys: @@ -3312,7 +3319,7 @@ The combinations of the shortcut keys listed below do not work when the `selecti ### Shortcut keys for editing -It is possible to use shortcut keys for editing a cell in Grid by setting the [`editable:true`](grid/api/grid_editable_config.md) property in the configuration object of Grid. +It is possible to use shortcut keys for editing a cell in Grid by setting the [`editable:true`](grid/api/grid_editable_config.md) property in the configuration object of Grid. No selection module is required: the editor opens in the active cell. ~~~jsx {7} const grid = new dhx.Grid("grid_container", { From 5a23f49ceef0df5da53b3e8e6469af108c861e38 Mon Sep 17 00:00:00 2001 From: Masha_Rudenko Date: Tue, 6 Oct 2026 12:40:15 +0300 Subject: [PATCH 04/10] [update] add focus screenshots to accessibility guides - Grid accessibility: contrast-light and contrast-dark screenshots in High-contrast themes, a focus-without-selection screenshot in Keyboard navigation with a link to the theme screenshots - Accessibility support: keyboard navigation screenshots for Toolbar, Sidebar, Menu and Colorpicker, each with a short description --- docs/common_features/accessibility_support.md | 16 ++++++++++++++++ docs/grid/accessibility.md | 15 +++++++++++++-- .../colorpicker_keyboard_navigation.png | Bin 0 -> 5361 bytes static/img/grid/grid_contrast_dark.png | Bin 0 -> 35703 bytes static/img/grid/grid_contrast_light.png | Bin 0 -> 35279 bytes .../img/grid/grid_focus_without_selection.png | Bin 0 -> 19144 bytes static/img/menu/menu_keyboard_navigation.png | Bin 0 -> 15914 bytes .../img/sidebar/sidebar_keyboard_navigation.png | Bin 0 -> 19383 bytes .../img/toolbar/toolbar_keyboard_navigation.png | Bin 0 -> 17369 bytes 9 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 static/img/colorpicker/colorpicker_keyboard_navigation.png create mode 100644 static/img/grid/grid_contrast_dark.png create mode 100644 static/img/grid/grid_contrast_light.png create mode 100644 static/img/grid/grid_focus_without_selection.png create mode 100644 static/img/menu/menu_keyboard_navigation.png create mode 100644 static/img/sidebar/sidebar_keyboard_navigation.png create mode 100644 static/img/toolbar/toolbar_keyboard_navigation.png diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index 2c4d5e56e..8faf09b4c 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -140,6 +140,14 @@ The focus indicator stays visible during the whole keyboard interaction, includi A text field inside a bar keeps the arrow keys while the caret can move, and passes them to the bar when the caret is at the matching end of the text and nothing is selected. +The *Skin* control of this Toolbar has its menu open, and a dashed outline shows which entry holds the keyboard focus: + +![Toolbar navigated with the keyboard](/img/toolbar/toolbar_keyboard_navigation.png) + +A Sidebar opens the menu of an item to the right of the bar. Here the menu of *Posts* is open, with its focused entry outlined: + +![Sidebar navigated with the keyboard](/img/sidebar/sidebar_keyboard_navigation.png) + Inside an open menu: | Keys | Action | @@ -152,6 +160,10 @@ Inside an open menu: | Esc | Close the menu and return focus to the control that opened it | | Tab | Close the menu and leave the bar | +The next screenshot shows two open levels of a Menu, *Format* and its *Align* submenu. Focus is on an entry of the submenu, which carries the dashed outline: + +![Menu navigated with the keyboard](/img/menu/menu_keyboard_navigation.png) + ### Colorpicker | Keys | Action | @@ -163,6 +175,10 @@ Inside an open menu: Focus moves into the custom color view when it opens, and back to the palette when the view is closed. +In a Colorpicker palette, the focused swatch and the selected one are marked differently: a dashed outline for focus and a frame for the selected color. In this example, they sit next to each other: + +![Colorpicker navigated with the keyboard](/img/colorpicker/colorpicker_keyboard_navigation.png) + :::info For the full list of built-in hotkeys, refer to the **Keyboard Navigation** articles of the following widgets: - [Grid](grid/configuration.md#keyboard-navigation) and the [Grid accessibility](grid/accessibility.md#keyboard-navigation) guide diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index a171ed630..e5aad7977 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -283,6 +283,12 @@ Placeholders in curly braces (`{column}`, `{count}`, `{min}`, `{max}`, `{value}` Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. It works in every configuration: without a selection module the keys move the active cell, and with one they move the selection as well; see [Keyboard navigation with and without selection](#selectionmodules). Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. Tab at the first or the last cell leaves the Grid in every configuration, including one with frozen columns (`leftSplit` / `rightSplit`). +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](/img/grid/grid_focus_without_selection.png) + +The [High-contrast themes](#highcontrastthemes) 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](grid/configuration.md#keyboard-navigation) article; the tables below summarize it. ### Grid body @@ -339,8 +345,13 @@ Navigation is **span-aware**: movement across merged (colspan/rowspan) header an 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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) 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](/img/grid/grid_contrast_light.png) + +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](/img/grid/grid_contrast_dark.png) ### Other low-vision support diff --git a/static/img/colorpicker/colorpicker_keyboard_navigation.png b/static/img/colorpicker/colorpicker_keyboard_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..56b39265ad31d366844ef905d12728d66d7bb8db GIT binary patch literal 5361 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 literal 0 HcmV?d00001 diff --git a/static/img/grid/grid_contrast_dark.png b/static/img/grid/grid_contrast_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..98e0b87832fad644d3a4440628daf0e6c44b212b GIT binary patch literal 35703 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%0D0`dp+m(6-XbbzI3=eG@D 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 literal 0 HcmV?d00001 diff --git a/static/img/grid/grid_focus_without_selection.png b/static/img/grid/grid_focus_without_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..05ae695966692b63d5256ef88872589024c2d4c7 GIT binary patch literal 19144 zcmb@u1yEc~+a^3XK>~ze!5u+1WQurG?zm}u|N0001{tc-**005s20Knm+BEx=Z ztG>pBeY~=l(Q*O+-Z;O!;MUB^-C;kXILj(Xq8y| zHGiu;L(eTZ6mtSte#;Qfh{<)))ym}#lz-7~3jZcsv|D`g-E2vEsjGf!wNk+*j>l!z z8TR(1rD>e98y-fSheNyIU-gNf;ez@+LOzOm;5Hf#5$V%g>8tBT7S?2^_1mWLku=U* z&oTbn(ms;Q}^2?3`TC1ZK!vcyqz zb#>VYF$jN3!M>#F(R_5N5^hpG{uMe9l>f2zr>dHo189N0xVV@tS>E|@3Mt_4kBA6# z4Lz6mnh1KE+~($#;^O!3nV9%}9{KR_@ERK%CyG?)3y^S&BTk0%G(#jLmcrpDdF&tW z9DE+nrJG$(!WXSwT^)9)nvaT0UP{J5mZ!rNy&qJn)Gqs-m^j_!WKrJ!a1^0UHrso% zuUsNdMP(!FcYx#yvm=UJG%6$n1vx-}Z8&^YN>Y+2CIo}|mX_A}U?OYOvrd(Om6Ra+xwE$dW)DHP+T!Bk>_gf8 zrG}cCBoN3yukXn$WrE(qzcb}JB?S%%D_v2W0+o|_OL6-A5zRNB%c@*H`JIq(5tsRx zI|?r`+bbtQJVV~d2QzBNxN*eSs{MysMik}Vp&LUZs)e8{h_Jl8+{SHF)RG#q8bssm zZC(*xi$vT+j9<$T3Sd~Qi$x-Kv;?oRzx&zP-0TQisJdh|#$9S`kYR@n%NGkcXq+lXXlB4sE^JbEAHTP7SriR+T4Wt zPwcGpHy@Pi@iRPDazvwrF9~l~CfJhLR_w=#4uf=52C6fKAtmL_XmNvqt7{`syU^{D zN9CJx8z9?Ky$ep^2g>7VU4gZOpjWv1ysk92(;q%-tCO<60(WPyOxY z^ZjKL)Po5!$7;Ii*}`Xtbb0Mf_|Rht?pTa8;S)}n6&h&OIPE8<@MdAJahp1AqRF`O z2x(+2IpkwQz7-w>2{^-FDj+1i#-xb4;ILnE)H{`!4dD;LUq(n8$_8G?kb56o~TOJ$f>T5E!S(Q2j}(RK?Os$)^}VH z$Qo`e4$5eA$p+{ID@Da2z_d#(jCA3%4kr@!dc*{qC53^;wMO`b3t6CGa;@EPAy+dl zpCKZ9j9y3dq*JUhvCx0-4T$%11Evd5xP_on(;iK3%~SU-gTnbMJmVp%m6t zFXES!p;#nZ#Mh-1HdZ3fw^s$ua%e$-5d1e#7hgK1$RFu)nwlg78$+!33@!N>ZlXPv zR_&&X%a(7KI%lZUVoXl|Hr)6`(G91`DSM%T**kJgA=0Mg8q`U#2P(PfdAKY@f}>Egn2q@y#Q6poi7 zAmrR|$jXdPp8XJc6s&Q?!{ zN2>U-Rg}rm?JPaPuPSu134IK($?6Q^ZSa+OOX;~=B8OhnwfgW!;(YaMbOR_R#I#uo zr&fPZUBGPgxTV``Q0UkP>2`HhASks#MfYN3Yn#eN?r%tk1_X)cwwI;dHZOCz&}@)b z5KcF80CZ_y|vJUDZXU_ZRxm)4PCMt1O= zZkil&u``WJxREekuVJv6jd2su5NH0(r!_4 z@psQ8_pTeJuZrxLsc(aEgdqFw`JzkFLnp%vm8Yt=>PG4!t6=Zg!|828 zx0atXyu3443z~P|4zGZvcbX*3Vf z#}Ct)q)Ml8-2JYC!}m%ii<#?en}%rUiTTR@)Jcu|^(vnvv=t1$8azMmp?Je4@P%rl z*)g~k$idT>YlebhwQdZVpu z?nV`yCctxm&uX0@rtM?(`4#O?g=xX0uGoetlD`tp)1LyVBi^K>9?oZpha^CI%=ZD{ zbiQKAzX$iTlJ7=v6l`tF4}z4NQ)|6NBruMDCgQMp;IMyZ6M;-E=%JB}Y+VLCV??5L z%>tZnvY@~I-YI`V+L*PY8W$ni&%37wR>q_g~g^y9Ywbh2~Ch|AP_bq<2V`E zMR~_c*{&{c!)hdU#`ccQ8I!7!euw;nHC1T*sevByZB_db(Wq_+Gl zGm5|ot`j|QqzUn+ooF-YECQS^0sMt1EPdhV&uk_SBdaGurT~`dY z!(Ab|;^QN%U16T~PPGx4dVBg)6M6on4sPeID@08UirrG?i|viQn+Ybi6}~RWdOn{2 zOJLwz`8)9w3KqtVHBZI;1UuLIK$YS5MgLV&42$PgiBaqJ5ju>5%u7>R0&_GI+IV`0 znIiNAi;{2$;C)u07_UL_T;|t_Sp-%UN?K9C@2_*KA;(7@6~Z2zwbG;OeYmE+zXn3I z@ZTTh%Olqv%+U7c*_-f*Tpu@C@U|-Y$Zi^a&mZAZj@H40>QP-w!cjCd0}Nzry}J&sqw_$IAR7lj^ea+g5nqKd)scZPX&`2m+rGii*u`Q#O%h+xE0yD zB;#8UQ(H4@ z=5^hQ#9ALaacTl;&o;}i_mCU7c6U#1hp`9X?tUd!<66nySboap{nl;l0&q&cLxlNQ zl0O{=?&%e4qs>Y#ZcV@J9YOo8 z8v21>yPQ}rn=YF+Yb&UQA2@+xJw=-{tRe#3BM)cEZsL%&iN~jf`1p8cbo_Bj)Sw$R zf@9rdzxf!i54+b>L&FUGhe7>y9|_!6BA7{vp&8^=zn*Mf#sB`QX-dt&(S;M(#eYrc z>3oX3u2VTvAudXiRVvQgT5h>I-9&heGM4o^EL68k``%?K5v32SbF?{*-#2Nn`1knT z2OHwk)*JjxIIjD}138J`tUDie$I(SMYHG1Gxl9<>ozKiBNZWt#3j5jyKap1+wZZ;k zhQ~?+7NSl>&{?xjbGoF`_pBiFz}zVB3*Lphso0BOa%xMEU~iWQoL`gQm0Ca3kNViR zGR|)C_6NL(`?*8@BXvN6?i4060*{UrLtVSZ?v>pjMi&OVmCMp!-|wZmCq50 zcExJ8`@3Dc#uD>78EFU-E^83lCGnANtq^QbtxZtP<=z9*Dpd1hoRe`?s;=MCx|xTBCZG zq$F;myl&mGlizj@CUf?Eb7XQb+!2GeGaG)a@@)J%u^1_&gbonca% z4+$gAiDbQQ$H?G%*IkVH!He;Jrkd|MA?p0#Q1b5sVl39u9~cDmB@XJqVGCw+bY>i0 z(;pZn#%x59!hRkW_fj>=iOpw9@Ri}yQ?J;`6Q+!OzMQ~c#hCkuKIFwykl8W$>6;1m z)2Sjgq&tOJ`E`Ef&hBr{+n41^3v;>+3|9KWd1X*UHWR%DLzy*7S(TI6QRN2-Tl`-ASB z1<;SGWP$m=5zn|%DoyXhngp4~23pjX8lOA)Xa2#O5EySxNzEn{jt<;h8$Dfrb6?^v zWQ$J8nQ$pnvFB#@SyB>7DyIsQ6@$l-_XD+{A&IzW{RakqgMZk84M%jH@#@sgxD>br z{oSL0qLZ#2=_u~){q#zUioSl-s;%8q1vz)l$uX7TFf9qe8xi9Irm$;Jf=Yxg5>BDw$mh?r=8l@?9_?U`EBmeeTXMbk0mb+Jv$A z%bv=;092TO0<$e$z(oK`#gp|?!iB{_4w_i%9M3fhiz4Y8oF>ikjaO1YYfl@-Wr1ts z6_E5hmXUQd78Vv~@Je!b2-f!IW>zXH3ks?H9ahRnO#{e$I^k{g>kR(esTu0f=Z>)K zTg=fAY?w$xBH~3@v$&FfE$(vu#=-9xOSko95vZ!I$bw~Lowh?1{l4k& z!8DuxDAF9cM3$t{y*;d7!`iUwNk^0AIqi?>dSA~HT(1;IB%)ZnaD@7cRkwA$LGeqQ z{iBkKK0`Kpm>%=~{d<@$p z!0Q9pk8;oRwUAILs%UD4CMG72RI(`VK%aS+){nioO{!*WyykR*#EV;k9%Qe&+gLrqByxBEB^o^aj%r*b=@XAu zH2-l*YmX_HDK`R2zw%@WqZd!tBaEq^KKeM5B9h2h<7V8JMUJKTx_h}k8dG*~)nO5>;a1k1YA0M~f7LUO5GEXw+2ud*;C+!jTyOX%5XgN8$*5w&|zLXzZOW zP<5Ctkv382Ud?8vAiwc5=J~5SOrFTf%)i^?v~jK6nV)jJtlAxd&r&HRtv$)XnWCAH ziiT>tCo%l?h6n_8HiPw$EG7B9=hV@UaHm3~!{5nF*Gg`;F)=&vJle!pe$P6S5C%Xp zOkGI@{Oxq8Uy`feQKs(cx$d*te{M5Qk$zne-;e{kT+iL(vu%sm5F?ql|~HwJ-Sglp&* z_&wh`O^drb?s7dOD_R8Vhu`t1GBcbCbzpru7sH7*~!($l^{5|wjp7B6IU+{H|bUgT?xoE%L1h*md5YHn%Ctk0r0=WUpt5=**T;zCY>yU@A@>!T)hM#Q0PA_rb0 z4(G_puEmc3nPg5)bb4wVXS1y2#SXU6!gn|p-L65nb%I`(dGJt`HJH$`U+*VB)#}@! z*gx5kZDu{M{oq9sbgODM1w_k}Nc=(%tctd&0GqBu4S#8jJ~J0D(=RkRM3_hzFNsI7 z26#7{Jn0GP@PKzV^`B59xN6Lt@U^7D{Vf~l9ExHx?u4X+7MCralvlF((MQtK6eEq% zHZ={EPUF;wBnsxkG2k8{(XdavaGPtty~&1;Y>M7uVPqAep1s;G*I#wm#>oJ(hqgK@ zm3AI78|+Lq#3i#|TX;Y&HmtB3tTla3I~E;LZOKH_r5{@0j0m9(W##mjG7lCYVaq*U z74x%+GJsJMx=E7C<>*C^Z;Gvz8A8ZZTL})pM1yyQYl|YW>atm@NO82%*ym&$xf|7d z#fo?`hU6yxbGe4TGR|M@qhFz}b-_3ex2KA4 zaN+qCdP;~OxX!YVv&Kqmb&Qgp7!`x!kE?GY03P0G_!b}S#~+a=HoG;kz6@N%x2A@) zfuSO#$4eBnPDFT|4S$mwrDwE! zYL($8m?C}ln5`GDdD z=;{R|5lY2tFh4&{AI!D&4>o=(CX#3hS7j$f}i=f^jnvmt`A3oRnGB$*hf6OOP$x%{+B?&&#+a_q8H>LuaOU&o!zCd8RqaB;k!HjM&OK9d2i6~JW3LfCC%}=&BL#C!N=x=tv?(GHZjCMqk3K)Y?eMFCucH%S) z%a`}&s;EgvNlD%CI@&`UBD@H>zQ3!%#ZA7FXZ;Ej|#J+C1+a(DsMr)NfW)8o$(Ek3udw@8GOcn_#n7OmnfwkrLCwc~U^+D+coH6Ep8~m`j#PUlH>G z8m^c}(k=J0T7(!MZr|5yt`f4bMJ^3%o}9JD>jz#JJ*5!z{6XjVPo#+>>*yGD3q&lQOs{%~Gil6#;M2P2XHvKrZ@3*C~NU$n=2AI$m5JAN*(x(Bh86+9YR^i!mkoy3SDA+x`m$tg*C_ ziIfPNiNYd`Ro;*tJf{USu;fTOCCIzO$anaZXQi6KDc+K1WQ<$kK4mgz*A>?E-Ui*nrK)=w`Zl;eU1S;et71e?ZAKb zYW1QLiu6*ycIZ+Qd@(Yj)Yn(cSf0dpS+0-M=o{%3po}3xaKVWio~E7jNi}3{Fa~RT zavF$~(DlX4xR6wIaJ*&qW`S6i2HmLtOm*^z!k#_4A*t07ctqq ziI)F^4q&9Jon9ki$S3OmlzKihSYT(8qjPC%|%!xI_tN%a%!O(n_!O&fmTKb zF|8YpW#JmOXNcrjuxsVuQencV1Tcojo)POLY+$kIDZJJIR_sjs50dY)jz(SnH$e zDX^@ZfyiF}UW8kKUXQ|#j!&UOO6Zq+;qx#-=fQB02f8Bt_54Ww%H&(nK z1zSjdU}b^^_5AdJhJ#Se%&*9u%h6Afo0Q;4gM@J%%RB&0)aCBxnXOQhtEjIMBfKGa zfcUQCIpY!74QFmEg4!9;U2d_oK}O@{94bnu1qf|Pzm;eEPb|Q`!1KH9?${Y)y0S;I zAN^E9np_VBrE5$0!{<@j-b}q?K`D$#Ln^U?~Vp&`MKG##~IIW09YCbA^K0W{Y)i? zLj0sZN-#Cfx4XUo1e6r_-OacGqAVD@-@DaxZ?6)6tE$4=AIutTZGP|c^`HaN_QMT$ zGZKnMmo%N^Roo9otO_RA zU~<6Syt^nF$eTG%-KRfx<_!S*d@T|= z?ZF}dFdAqmMCry%RJa+{$7~b&|MLAp?nzi_789e&J?YBrbl19^$*7a|BpnDcvKSe^IwWbcy5jPD)4G zFX;rZcnX&5e-p?5c>l!cXUAUU|MlE~Ic<}1(O3j54kOXy-*FfJ88!dYISoM&jsiWU zKuX}M&F?nm%xLvoLrZhs;rhDVw+l;!*e_-!ii)3*!%X)%_XsWJFHPmmyl!FEEa?Q1 z%FhJu6FH^cZ>4oG(#y%{W%NuA(4WEaSyHdJKEF@%bo!Rh6%9=Edxj z=_aIXq*D%Gv)6M{yxN0W{F!d(DT6tD>CsN!gd8`vUd`|Re(U3O$Jj<$T1<%rFBBAq z)*`78K|XR#PO?Y-^o$$nQ$qV;<_TYwlN}ZnWj>=~7pt#dp=!`JI8((gAuWx|>H6Nz zXHr6n{__42m$(gbH}8T=KvvtS@E6gA`y&d1zPD}vy7^zng{sxJZxv;X(2DLyg)IJ= z%ewhjN>zLEJ30pExK)5Y3S^iNmXn<_U`CNu{l(ro?^ow~>+-g+k2OI`^*e@T0`Vk| zckg%c&g9acI83m?9+QEf2y99kA2@HJtz^jQ^1;V82G&JnRH&@ z62+fkrjv{ZWzWD(h^=K0%fl7A=M%|L$opw}<;|0Px4pcr8hbhpzqu@5J;dbq8O_aG zvAhIJWPS!T@s|La!Q*&|mI^%CQO-32!VKRR2qF8blx@yz94Wd}7HRffkL^3X-KeUu z#-1OP5Pvkh>(Y9$5s}lpkFfmS^nH$$g5gpcp7kiR6}IWa&!qkoE$TB)I2Q9RUs8;g z+h0H_b9|n*wt&uc1vK2Q>i@>muZx4$^S$8o7LxoyKumE3foF7})J@W0J@4xsXP*oo)xg zCJsYRLIqcmr+(EpZaZJp-Ap`8srm7$M6srz_~KXVM+Fd^Zz!!0j-C43Wat^92$)p% zu2ZnF#g4B}YDn|zS7g0ahqBC@`_BnxkT;hzZ2r1g_7+rfYb%lcDh2*uqph0%&htcl z3NYE7DoBlgiJTgqcw{LkB9CN=ro|s)L=}#Y#>w1Z=tGod@5|^9FRB{RCK^hSMvyVU zmJ|DHzt_qg2dKeurN@3s%;(_1<#xiv4Ip0L>s~z{4*M`*+Fw9^dG1fI1$xsvGVh~Q zS+to!XF~t2@|_A%vccXqG0}b!HJZERYuc@0T2oW3zS{&R_wtvK;}|@*%58uf8Fjv& z7VrgfLnINcEpOg-pGEl8m{Wl@muAP99ol+YCK2^+6IIMliarG19y3Ek5uC4J$Sd} zzn(YMKd3X!oi=a?I!NO--Lvnk8HTUH+6t--Cuo+;6zc88p6tp-rfA~e1#bmCZd6&z zXb7<`5uE4Z2nKJS1Qb>Nj#Xj2yLG0&oql^U{IaJ(YmguN6&%rVS`@WI9fIy~dox+- zCHNojzs+~ zfiPQo@#*P$q}bShhher67zV5*tBa13TuiaIVMn{+L#A0roX|3xP3 z!R7lSRM+v}YgF3JozA6A|F%Z=E7rx%U~lF-OrQ=6aXlF&)#p!zFl|!CiUGT{t1wr=P0%m~O3C?0pAb9pB)K*nYdZf~xI zE$|3uow3l_ys~iy1jEPb$L89Gsn2lkjwjE}wH*a-;m-CkQI4iwK-b4E>5!wT=cJuG z?_AoVUKdLk_CV=8S{0qid*Wfo`k|mDeKZx!GJr(Eefv#sDg6~cz{~8uishs}u^pMB zV#i%C1$re9`27C;@l?6zgB&1ALw!EaB_mD$R@)8sp6JtB{q6@0S5>o=J6UQcfchWh}| zNv1_|a(4&V02lx&m4jRcBLE(S^9or`Uciq#Q&}yG&11C}V9w(AY@TrCjxPWPN?TcM zVCWP8L#uOnK^>0<02qv&&AWyHoK8OuPrW6~aWLGU6?L`5J+jnEQ{k5`+@D~D^@LK# z2-UxPmY<)W5D-NsJT#djovufQQ^4)PC9>$H>EXkq=jd=5 zNZ!8%>|=A<7oTu={l72i5%F2-Yy?303*843^UTU3#SYtIId61s4aP_C$=3 zai}w`U?O%0UwrK4;7&hA?Alm1Xy-mr8aDstmJz1u{cwtZZun+yZTH=ZhSfTf`l^&) zQwUq$@QQ{RG@X?%%Gih|_|8qDu(>%+|a$T_YM0613q`rGGL; zDWTgC*n>%z(Kt>%Eh%skKT=JZAS-7ya(qd+c_LG@Z?iSa&m_ZC=|7nMV<4H<%lm(M90ah9xIaCM~uz9Ko~uSNk$FFvSQr$x#O}@-EUDewm&nRdaA`0rN7Qt zQop;K3R)Y}xW>11$`r9!Re{kGL6o^>5*iWFHLh8c!yV#s`9>3B`E~VX|CumINp9Jf zb2;LfGgAw_KGp&$#}n{XsrM3Ma4;>tlm!BTj&8m~n;B=t^GnOtd~K!GD$+S}J%*a` z$;`BLgv|ziAJrw$)hB`q^@SkmM|M$yWEpqn;@4?J5hV?~q~#*6+074ub&U@Z+)l*? znisMz4(N&+Y0ZrfLGy04-_~o21vJ}i*3ym?fcvSFyQiIg2>Xej1|hTKSj5W0xjw>0 zD=JS91C+BW>b^SOt<}vB?t}faPJI|fQF0=>HQf!f!{kKD!n;2~N8ujJnd^>?MKz%^ z)Y{8Fw;za9gm`TyZ#;S>F>vd zUWm>4_$2tb>xeKi?VT%o37yyMRk>bS`Lm zd@x||#aS<(YXY*@y6;cMM}NSOU<}6j7z|%{d&oeX?vb+9fTf|Se+xu#wNh$ha_9Ru zo;SIS6ZidYQTJ?gdk96(h+9H`UP^5s>^wTXuer3P_vw}5X>Uji6BaRFY9(bxMZFjt zgZCA!XntF9>_x#F5ZN7H;Cj52dVU@kc7UvET6r_NN=TJy=;0+aP5ydo0Hs8Re@5m8 z#&^7}h?;mKHx@%|#!P>aF7%Wzvy-^zr2fEz=WKG5qTQwXH?5%})3AOVNOKF-8b3rCU{S%Af# z+pG2IPi<&+gt|uzQ#`rJ9<$k&4L8<_nQ6Y4m>-n%s*E&+b)efnJ{rmsesJ(DTe_D6 zD#~4US`5cBRWfdgTR9dy)MR98DNx`Vt= zMd!SIGKOVpP1ncofZ$k5gRHmbJgbqIbe(29BRt#q$z!VIooJ6;kI>vm@sqhV-Q)b< zN>8lh(e#oT|K2@G-e?us@&+u69cJkTAtbN^?NrDQqTr;ig?PR1D?{zjk52?5ouCYCSoVea9pLE4ASjmDe3#Fj8a2 z5&H?OfDf9lYd2itoHXJGQ_(q^w!_#lJ;mY9OhzKZ8ruovM%pGNqB9iC*OOb>7M^BU zSPDpuB_vpio!?~Vghdez*Pig6uNZ)Hz+-R(cJQ7k_Wj+Ln(bbUoG9IM9g2b`V+?>f z($=Qx$U>tS%#^D0TAsD4=YYK>mJM32eL-gQ{OE<4o0be31$M9Urz}%;t~ocTTr79u3qxDbm+=0 zx#B(t^szsZgVWTz8eHh8fIgaDA{}r7jzgY5&q;LgGz;-?WBW;gIP$If079sZcE0SwJ=E zi0}7zoVL1SBM*NIL;1ckI1ux`v0$ZkQV^OZGZ-21Ris%!IzzMA%IrG6aV94o@2Tj& z0Q+7LZfl&D6mZA&I$ZJ#d9bh|8a!H1Os(m;`hCk$To~t2no@l5*L;H(%(-8djF|MZ zQqb?9qn2X5M8SDWGba^8uMHK%`@7ReyvmH0o!64H1)+a(Ibo=p*KL}4?^XW#R(O^; zEIgkec6El|3k8?AoVWa^HOJjr_l`vu;|P>>@v!K_a{H@_LWjtf(VUpJUH|u*{jJv- zky`K8?)1P0-XnCLZ#kuBF*02uOovWI=;$n&68=UpQl_nmSFmDGjTt%9yI;vrt!F4L z&EvkZ<@)2?KV4&v+U|;xJJi)Isz5yoMbfu3#l!c+!}Q=$npt1ah1i)x!lvt&Qwh&; zn`ZP}#Vx-C(R-5jh&~KiXU%I8h)u?Y-q)#`r_-yWnC;_seEl`H>u4VgEKKev@mQg2 zSH5TL8H5Ll&-;tla}>g2vQ=SST~cr_TDaX`=tA?{ovX@D=g-h< z^UGudiYQVy-N+T;^GA`?R+aQEO|%QeQFh!Ip5KG1+w^C98U+O{#^9YhCOfkaxCwKa zf6V&7BaPe6^O31*$)@uDyluG%>tTb?wVJsmN*)}K=seH3_= zJ~RQZS=8{++TC&^YIzg&D28mhi6&ie!HMnlIAl+53Gj=>>ZGw9s&P+f0@EDEq<#45%SYBCR=}$(} zreJJ$>WL!s@{sC#jt=m^HW04OI3zYif64&EsO`a-}BborByy5I({thht*W<7wn|#XGE2 z&GRfIPrL;=POTPp(p!-Rfd}PBU&&kzfn#WVKph$=Naudu9DOs!n_EpacNEEfT0ye4 z#Veh9p(Zh=$&p!bMZBplzh;`10UH7dD;=&wa2-Z+L4Cf`mf`y}kG25Yrs;6+phy#Z zID#Rt%wfB4YrWCzuUPH3dJ*Yb^@3M-WY5}I+3aSD_=U3&PPbD!Mkvf7tF32iZ~s4d z=E;YBxX#2*3Yjq`L<4EJ66y8xp62{tt(LDC^LP#nyXyMKiTI5QO~ML@!A80>+UjMB z6lGI^^Q@zyUzlGsYpNyv7f0r6A*7l@``$ryqM@ibX2!?mTjdgAcvtaxv63;-T<{BP zc5M2f=^aK+hyNECuW6EC#BKu!J^ZB3cY0?DN!N>gCu;u7gCXs_`%3U1x%063aW7OL z=+(O4cB82PxnKzP<&PS4N*EhWG#9kVfK#tLa8n#XvVXly7)zmY-ojwZTv@dMXT-tu902CyMVzde&T%N^KFlSIl<( zZ%A9fkyq&X&bZUypXRQ`ZAKnybP>{$xGnjkV=EsA$INJ+2o>)z0X!cB=c~_l-M6;4 zfsB+f(T-MPCuF$2{K zVwsO|7foUJrXh&f7z9tL&iPLLH2V6S^dnX&F{TmyK`}|u>(QAG%bwP0_p@svLed#I z7xsL5sudYbf)C}3c0!o+cT4WzK(WdpF54+h8*gDI$-cR|pgGNqSXA{$NH6{XO2Qp3 z^vnrscY9kUH(jL;XO@~YXUMlQnZs}5X`@t-PU;(G-gVK+S$##!9y4&^QYuph#IJXr zqua%KXkkZe;forDxc!${{R&ign+DS=6+lxiBfebu=WEL_I=m*B5D4XZe!qMAx!cu~ z?^)^xhhq)e`NOPmpfe``dBvCLQ)nuZoY5#;K1G)uGtF+?V@>Oq7ca0G5A*wmlluHj zr!z0Vd(REzB3Y@AYuE$kmoE;b`mRfSMR(*}CzW2YN&ettgKPE^Y(#rvry}c2Gi?PdwI;fK{it612o;A`%UGBb*L@_<*3e;^63uzM4HA zEb(v6?FAhO__MpEkXo#Yq%Q25tz0J3@U$oPl&;~w$w>Me<>6qq2FWx`V@uriZAwJ; zE}V0IlVJdtWAWzJ%87cMF%$IR?Dn!42vWSuITxCnhXF%+CI%+A{sKXh*5D(lC4VJ9QKC}MnV@?I5-dD%HBkOB2 zr$&G2=JJs_e3WUxQf#j1dkwe2vA322l6Tl1sq}}*QJvElY62@nvS(mt4mdQ&f%>2~ z9&+0jH$);C49fy<*7f_t8<;}V*xpDFd^ei*JVX@2mj2y=qZ@hmGu*;>+H$~r=H?K~yOO5&^;%!wSS2uE$RqG|BRuc0RZH7)L}x_XX&@iZBy zPGavshq*l3lpRs`-C7--84l!9=+*1OdGRno5#Hp%nzW;RZnq+Elr>R(m0{k^80Gkl;Uuloqr?tZb?Pe{fR8E`o(n#+jBQdHGwvM3Nm>P zyDx92v77d{aaS%t?PbT{U1TWZ`oHW;17c-0?o z=QqL)2Zu%<$L94&N>h>4BmCpQ;0VoKBDZ(`bHf|$--^xLzjdB;RItgj!I-rBC%x9p zmE!T~t(dP$7YK&V{xWMW4Za<^lMaxDE+6M|<43q&{_R(lv3xT7B&Q<#*l*k18%e#@ zOefGf9QEa3FF*-b{NZIp5d>U@>>YC^r)FRQqr8H9*vj=?#&X>0eS$1^7B|`}f;z^(fzQwzFdxs*L)7+N$XnJ`TRqp@2bu1yG)gj- zEAmQNbdRWoUlTkks5ZFF)7LfM}8IMs0Uy}B1sx(wR6^%fBtcfvwaPEF8% za(#Z==v%4vlR$r_yqO!VkM_3fN5+a?PUlB>8q2Z+`m$%s5r zsWDmfzIOig%ftp3u-xGavEbF_j^3$mR;Gq%;WF(L#DJU;vW7N_2LSxwuMx}owS5aw zxb<_sZY$Ba@y^Fnsvz=B{Du~W{XC zYx9h&d3}}D5&%F`dU-AYcl6bcNa5`8!&OLq2IC|7MfZ^yj+qMU96^z|6CPcs8#a9^rnE~l)h80F~b zD1Qr2i1SY!Et)X_mWEI8obwwS6b*ZD0DvL#r?)Tzq_8Dk5{Ung5+gjT05%iMj5(`u zq}iKsINIu!3;a@b1)HWv@cF~NB9a^8>>6%jweUqnsY-XSS5w8k9NfyguXQuF%1%2M zWqxI4#Z13)8@oaRmS(3IJvAN2nUvL@%;QIh*V)O`<+fQ!kE;9UedBpVb*Sv--X0h2 zAzUIPQ@usw|40Y9npT?TD_-Y+7*;Mg>cv%rhHmp^-U@{Hd_7VH-9y~l<}Dv&i&QSZ z4>X+xZAW^!b}eeD9a-P2KovkFA3cue9Y_l1Ej_ZkEI}Z*2ME0gfmB~HHR*EtR5gD9RoRhd9Gq$3||f z+&qn}=-kI$IG}E9wfB3iI@NP~&9AaYKqb=sgzwkeK20?=8nz$tN?MOZX3GLM$KYg? zJ3yBS;46=vb9t{77TM2*B{?c{#qaUPU1YUb?&s7Q*d~f@J!vrA4kZ%vVuy^iwvRi! zTs|V`oib%J&it!%)K70^`6g&wo>rS-$sE9_V>!D;9IP(`6p+v&(Zb zzKgJl{-Q+9{wVN-Hw)EtJ(c)Y=7*vtW3;i*yk&2iJ>~v)n*nrub%_QbxW0Y z7o?GaTMmOm>C}U<@k~L~aph4_QZ9SAatYGP<$3&QC2kkJ)6wG$9wB-BDPqF2UWPst z0oW3L&LcX-c}_~l1AgEtRwhj8S)WEd*JtHj3pwpZdB3Itmz|HA=U*8e%Jop9B-P$B z?0jW4e0RpIpW8F}^!-p)=JWqp#)G>HzRlkx67g>Glc2-}9^W>mZJqD!vAD1`-hN5n zPDb5m4b9yF{GzwAtZ(R9D`pz3d8RD8DNJXwKP<*3L@o?C@$vWHWxpAEI*%p+dnCL6 zF|U8!`fXa*sn;Q!ubt;!**II{?N{xSc9K`VO}{1D>8;lvuez1DD(U1>(OF-dezzW7 z^)s}$>)no!eSccN?rYE9lA1hm&HBoPe?`BBd~W>N$Rohl&bjD7@0=41GmidZOZEh!T04|ie4Dpp%G-(csdJ^wo}8}Dj%J;3>gM*4-&e9PC0AeBam{+4 z;i_r3e6#$boBH+Wzs@(5pnYX?A8~Yez?l#=|wr|;q z6;F2O{C4e)DOg=`Ixh65`nFwnCQS)&e{5`J6t&nkU+nZRqeatnx32pC*EZ|cgZa=T zTwIVXw?mWdO4$zIdz&t8oE&++`jy4@kk#_vPtR4|S;V|5;%Vsjm3gW@+qoWSe=KjR zdiyzvwdUm1x|`2+@4ozc;_=KMJ7Oob?Jik#I>eQ`V^JL=p)^7Xt^t$cj!>=I)R=pN9X*vn8=0!NkvPUo3D$3sJI`J+xY6^Rvvr#(4g}D*}Cee{ad1dgY(f-_-LxyCz-xk?Xnt z(JZK0zyzEZ62H5z?B~}Pr?lU_Wt_3DYL;k?b^O)-r}2C3wtHNSejPF||K#J1Q}(ZO zy!!p-D~rmn`kG80{Ei|!9PhgGX~smXdUR*Wp^QuCZSJu82nTN6b44lp?qsc5pRQl9 z-K2B!-Ru8Z-b$Ih@jg~4wjL>Hzty)hBu;Y2pK~)|<&^Km&4QZOCzYz8C&uUh|n^x}BB z$lk-3WHd_=<(0HRuI-|8)$*a4pJIQuo)(!=9p%o~A}T*s==rVE2N5BzjZSi3nS7#y z7GJw|&2zr(s!yK#N~^EjiqKoHa{rO5m(1Pu)8@4-X<@s^oAqDQyYZet&e-ZvQmPn8QbZwCeb^aGxKlF2ed z@YCw8oAp+_jlRCZQg5eC-Bc5kRj1TF^;omYPvzZN>YLpJ$~V;&o419={SwcFTJUU! zC|9e@{`>Q*{2xebiFIr0>z^-?X1H{Kx&@V?Hn3H)Y&Ni%ad`GqK3OZh|G!i7P9T@T M)78&qol`;+0DrM@DF6Tf literal 0 HcmV?d00001 diff --git a/static/img/menu/menu_keyboard_navigation.png b/static/img/menu/menu_keyboard_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..43bbc708a28f40c09c79e1c47d3e5f5dacc099e4 GIT binary patch literal 15914 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 literal 0 HcmV?d00001 diff --git a/static/img/toolbar/toolbar_keyboard_navigation.png b/static/img/toolbar/toolbar_keyboard_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..62cc218a1789807191009c13fa684fe92f2607ff GIT binary patch literal 17369 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 aX)$BPgfz?9!Jp-T$h}pTDv~t$_&)%^uT~5I literal 0 HcmV?d00001 From c4c3ce1060f1985d92dc2380bc7d481bebecf640 Mon Sep 17 00:00:00 2001 From: osorokin Date: Tue, 6 Oct 2026 16:05:12 +0300 Subject: [PATCH 05/10] [dev] Fixed doc after review (DHX-5290) --- docs/common_features/accessibility_support.md | 6 ++-- docs/grid/accessibility.md | 30 +++++++++-------- docs/grid/accessibility_conformance_report.md | 22 ++++++------- docs/grid/api/api_overview.md | 1 + docs/grid/api/grid_arialabel_config.md | 29 +++++++++++++++++ docs/grid/configuration.md | 32 ++++++++++++------- docs/grid/localization.md | 6 ---- docs/grid/usage_blockselection.md | 2 +- docs/whatsnew.md | 4 ++- sidebars.js | 1 + 10 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 docs/grid/api/grid_arialabel_config.md diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index 8faf09b4c..1876b2cce 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -33,14 +33,14 @@ There are special attributes used in the markup of DHTMLX Suite widgets that mak ### Grid -There are **roles** and **attributes** for elements of grid, sorting, filters, editable cells to enable screen readers to interpret and navigate the columns and rows of the grid (enabled by default). Custom content should be marked manually. +There are **roles** and **attributes** for elements of grid, sorting, filters, editable cells to enable screen readers to interpret and navigate the columns and rows of the grid (always rendered). Custom content should be marked manually. You can find the following **roles** and **attributes** in the DOM: - role: *grid* (or *treegrid* in the `type: "tree"` mode), *rowgroup*, *row*, *columnheader*, *gridcell*, *button* (the sort control and the tree expand/collapse toggle) - aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort* (on sortable columns), *aria-selected*, *aria-readonly*, *aria-multiselectable*, *aria-colspan* and *aria-rowspan* (on merged cells), and — for tree rows — *aria-level* and *aria-expanded*. -The cell surface is a single tab stop, and the arrow keys move the active cell inside it, whether or not a selection module is enabled. +The Tab and Shift+Tab keys move cell by cell through the header, the body and the footer and leave the grid at its ends. The arrow keys move the active cell within and between these zones, whether or not a selection module is enabled. In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. Dynamic changes - sorting, filtering, data loading - are read out through a visually hidden polite live region, which the application can write to itself; all the built-in screen-reader strings are stored in the `aria_*` locale keys and can be translated. @@ -122,7 +122,7 @@ The buttons of alert and confirmation dialogs take their accessible names from t All DHTMLX Suite widgets are provided with a keyboard navigation support. It allows using a Suite-based app without a mouse pointer. Basic rules include: -- the Tab key is used to navigate between widgets and clickable areas of the widgets. A group of controls - a grid, a toolbar, a menu bar, a sidebar, a ribbon, a color palette - is a single tab stop +- the Tab key is used to navigate between widgets and clickable areas of the widgets. A group of controls - a toolbar, a menu bar, a sidebar, a ribbon, a color palette - is a single tab stop. In a grid, Tab moves cell by cell, and leaves the grid at its ends - the Arrow keys are used to move selection or change active elements within widgets - the Esc key closes windows, menus and editors - the Enter is used to open and hide drop-down lists of select controls diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index e5aad7977..2b4206013 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -19,8 +19,8 @@ Conformance is reported criterion by criterion rather than as a blanket claim. T | Area | Support | | ---- | ------- | | Keyboard operation | Full: 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 semantics | Built-in (`grid` / `treegrid` model), enabled by default | -| Focus model | A single tab stop for the whole cell surface; focus moves between header, body, and footer | +| WAI-ARIA semantics | Built-in (`grid` / `treegrid` model), always rendered | +| Focus model | Tab / 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 model | Optional: single-cell/row (`selection`) or spreadsheet-style range (`blockSelection`), for selecting cells and ranges | | Screen readers | Tested with NVDA, JAWS and VoiceOver | | Visual accessibility | Dedicated light and dark high-contrast themes (colour-blind friendly, AA contrast, 16px base) and non-colour cues | @@ -50,7 +50,7 @@ As with any embeddable component, the accessibility of the final page also depen The Grid ships accessible by default: WAI-ARIA output is emitted automatically, and keyboard navigation is on ([`keyNavigation: true`](grid/api/grid_keynavigation_config.md)), 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: ~~~jsx -// 1. WAI-ARIA output and keyboard navigation are enabled by default - no action needed +// 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, @@ -61,7 +61,7 @@ const grid = new dhx.Grid("grid_container", { 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](/themes/) for all the options. +The theme can also be applied through the `data-dhx-theme` attribute of the container or of the root element; see the [Themes overview](themes/themes.md) for all the options. Add a selection module only when users need to select cells or ranges; see the next section. @@ -152,7 +152,7 @@ The sections below describe each capability in detail. ## WAI-ARIA Attributes -WAI-ARIA roles and attributes are added to the component markup automatically and are **enabled by default**. 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. +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 @@ -162,6 +162,7 @@ The following table lists the container role and the grid-wide attributes, which | -------- | ---------------- | ------- | | `.dhx_grid-content` | `role="grid"` | Standard grid | | `.dhx_grid-content` | `role="treegrid"` | TreeGrid (`type: "tree"`) | +| `.dhx_grid-content` | `aria-label` | Accessible name of the grid, set through the [`ariaLabel`](grid/api/grid_arialabel_config.md) property | | `.dhx_grid-content` | `aria-rowcount` | Total number of data rows | | `.dhx_grid-content` | `aria-colcount` | Number of visible columns | | `.dhx_grid-content` | `aria-readonly` | `"true"` when the grid is not editable | @@ -179,7 +180,7 @@ The following table lists the roles and the position and state attributes applie | Data cell | `aria-colindex` | 1-based column position | | Data cell | `aria-readonly` | `"true"` when the cell is not editable | | Data cell | `aria-selected` | Selection state of the cell, when a selection module is enabled | -| Data cell | `tabindex` | `"0"` on exactly one cell (the active cell), `"-1"` on the rest, so the cell surface is a single tab stop | +| Data cell | `tabindex` | `"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 cell | `aria-colspan` / `aria-rowspan` | Number of columns / rows the merged cell covers | ### Tree (TreeGrid) rows @@ -281,7 +282,7 @@ Placeholders in curly braces (`{column}`, `{count}`, `{min}`, `{max}`, `{value}` ## Keyboard navigation -Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. It works in every configuration: without a selection module the keys move the active cell, and with one they move the selection as well; see [Keyboard navigation with and without selection](#selectionmodules). Focus enters the Grid through hidden focus sentinels placed before the header and after the footer, which direct it into the correct zone. Within each zone a single cell is the tab stop, and the arrow keys move between cells from there. Tab at the first or the last cell leaves the Grid in every configuration, including one with frozen columns (`leftSplit` / `rightSplit`). +Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. It works in every configuration: without a selection module the keys move the active cell, and with one they move the selection as well; see [Keyboard navigation with and without selection](#selectionmodules). 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: @@ -308,7 +309,7 @@ Shortcuts are organized into **zones** (body, header, footer) and resolved by wh | F2 | Open the editor of the active cell (non-boolean) | `editable` | | Space | Toggle a boolean cell | `editable` | | Escape | Cancel editing without saving | `editable` | -| Tab / Shift + Tab | Move to the next / previous cell, wrapping rows; exits to the footer / header at the ends | — | +| Tab / Shift + Tab | Move 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 | — | | Delete | Clear the selected range | `blockSelection` range mode and `editable` | | Ctrl + Z / Ctrl + Shift + Z | Undo / Redo | History module | | Ctrl + Enter | Expand / collapse the row (`type: "tree"`) | TreeGrid | @@ -322,7 +323,7 @@ Shortcuts are organized into **zones** (body, header, footer) and resolved by wh | ↑ / ↓ | Move between header rows (multi-row header); ↑ from the first row has no effect, ↓ from the last row moves into the body | | Enter / Space | Sort by the column; on a filter cell, Enter activates the filter control | | Shift + Enter | Toggle multi-sort for the column (requires `multiSort`) | -| Tab / Shift + Tab | Move within the header with row wrapping; exits to the body / out of the grid at the ends | +| Tab / Shift + Tab | Move within the header with row wrapping; at the ends, Tab moves to the body and Shift + Tab leaves the grid | | Escape | Deactivate a filter control (restoring focus to its cell), or return focus to the body | ### Footer @@ -332,7 +333,7 @@ Shortcuts are organized into **zones** (body, header, footer) and resolved by wh | ← / → | 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 | | Enter | Activate a footer filter control | -| Tab / Shift + Tab | Move within the footer with row wrapping; exits the grid / to the body at the ends | +| Tab / Shift + Tab | Move within the footer with row wrapping; at the ends, Tab leaves the grid and Shift + Tab moves to the body | | Escape | Deactivate a filter control, or return focus to the body | :::note @@ -343,7 +344,7 @@ Navigation is **span-aware**: movement across merged (colspan/rowspan) header an ### High-contrast themes {#highcontrastthemes} -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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. +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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](themes/themes.md) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) 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: @@ -358,7 +359,7 @@ The `contrast-dark` theme draws light text and borders on a dark background. Whe - **Colour 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 the active cell is the single 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 colour, 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. +- **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 colour, 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 @@ -367,7 +368,7 @@ A few accessibility requirements live at the page level, not inside the componen - sets a document language, e.g. ``; - provides a page `

` and wraps the grid in an appropriate landmark (e.g. `
`); -- gives the grid container an accessible name where multiple widgets share a page. +- gives each grid an accessible name with the [`ariaLabel`](grid/api/grid_arialabel_config.md) property where multiple widgets share a page. ## Testing and methodology @@ -386,9 +387,10 @@ Accessibility is validated continuously, and against the component source rather - [selection](grid/api/grid_selection_config.md) - [blockSelection](grid/api/grid_blockselection_config.md) - [announce()](grid/api/grid_announce_method.md) +- [ariaLabel](grid/api/grid_arialabel_config.md) - [Localization](grid/localization.md) - [TreeGrid mode](grid/treegrid_mode.md) -- [Themes overview](/themes/) +- [Themes overview](themes/themes.md) - [Accessibility support across DHTMLX Suite](common_features/accessibility_support.md) - [WCAG 2.2](https://www.w3.org/TR/WCAG22/) - [WAI-ARIA Authoring Practices: Grid / Treegrid](https://www.w3.org/WAI/ARIA/apg/patterns/) diff --git a/docs/grid/accessibility_conformance_report.md b/docs/grid/accessibility_conformance_report.md index eea060ebf..76a334c1c 100644 --- a/docs/grid/accessibility_conformance_report.md +++ b/docs/grid/accessibility_conformance_report.md @@ -8,9 +8,9 @@ description: You can explore the accessibility conformance report (VPAT 2.5) for This report documents how DHTMLX Grid conforms to the major accessibility standards. It follows the **VPAT® 2.5 (International Edition)** structure, so you can map it directly against your own procurement or compliance checklist. For a task-oriented overview of the accessibility features and how to configure them, see the [Accessibility](grid/accessibility.md) guide. -**Name of Product/Version:** DHTMLX Grid v9.3.12 +**Name of Product/Version:** DHTMLX Grid v9.4 -**Report Date:** September 2026 +**Report Date:** October 2026 **Product Description:** DHTMLX Grid is an interactive JavaScript data grid component for building tabular interfaces in a web browser. It renders a column header, a virtualized data body and a footer, and supports sorting, filtering, inline editing, cell and range selection, frozen columns and rows, merged cells, and a hierarchical TreeGrid mode. It is embedded by a host web application. @@ -18,7 +18,7 @@ This report documents how DHTMLX Grid conforms to the major accessibility standa ## Notes -- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output active (enabled by default), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. +- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output (always on), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. - DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities - the page title, ``, skip/bypass mechanisms, and site-wide navigation - belong to the host application and are reported as *Not Applicable* with an explanation. - In the WCAG tables, only the **Web** response is populated. The *Electronic Docs*, *Software*, *Closed*, and *Authoring Tool* rows are *Not Applicable*: the product is not standalone (non-web) software, closed functionality, an authoring tool, or a piece of electronic documentation. @@ -63,15 +63,15 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **1.3.3** Sensory Characteristics | Supports | No instruction or label relies solely on shape, size, or spatial location; interactive elements carry text or an ARIA name. | | **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. The `contrast-light` and `contrast-dark` themes are additionally colour-blind friendly; verified under protanopia / deuteranopia / tritanopia / achromatopsia emulation. | | **1.4.2** Audio Control | Not Applicable | The product produces no auto-playing audio. | -| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`) and works in every configuration, with or without a selection module. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End`, `Page Up`/`Page Down` and `Tab` move the active cell (and the selection, when a selection module is enabled); `Shift` combinations extend the selection; with `editable`, `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column sizing and column and row order are also reachable without a pointer (see 2.5.7). | -| **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. Each zone is entered and left with `Tab`/`Shift`+`Tab` through hidden focus sentinels, in every configuration, including one with frozen columns (`leftSplit`/`rightSplit`), and `Escape` deactivates a header or footer filter control and returns focus to its cell. | +| **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`) and works in every configuration, with or without a selection module. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End`, `Page Up`/`Page Down` and `Tab` move the active cell (and the selection, when a selection module is enabled); `Shift` combinations extend the selection; with `editable`, `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column resizing (`resizable`) and column/row drag-and-drop (`dragItem`) are pointer features; keyboard equivalents are added by the integrator through the public API (`setColumns()`, `adjustColumnWidth()`, `data.move()`) bound to custom shortcuts with the `beforeKeyDown`/`afterKeyDown` events (by design). | +| **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. `Tab` past the last cell of the grid and `Shift`+`Tab` before the first header cell move focus out of the widget through hidden focus sentinels, in every configuration, including one with frozen columns (`leftSplit`/`rightSplit`). `Escape` deactivates a header or footer filter control and returns focus to its cell. | | **2.1.4** Character Key Shortcuts | Supports | No shortcut uses a bare letter, digit or punctuation key. The only letter-based bindings are `Ctrl`+`Z` and `Ctrl`+`Shift`+`Z`; everything else uses arrows, `Enter`, `Escape`, `Tab`, `F2`, `Space`, `Delete`, `Home`/`End` or `Page Up`/`Page Down`. Handlers are additionally gated on the grid holding focus. | | **2.2.1** Timing Adjustable | Supports | The product sets no time limits: no session timeout, no auto-refresh and no polling. The header filter's 500 ms debounce delays applying a filter, not the user's input. | | **2.2.2** Pause, Stop, Hide | Supports | There is no auto-updating, moving, blinking or scrolling content. The only motion is hover and state transitions of 0.5 s or less. | | **2.3.1** Three Flashes or Below Threshold | Supports | The product contains no flashing content. | | **2.4.1** Bypass Blocks | Not Applicable | Applies at page level; bypass mechanisms belong to the host application. | | **2.4.2** Page Titled | Not Applicable | Page titles are the host application's responsibility. | -| **2.4.3** Focus Order | Supports | A roving-tabindex model makes the whole cell surface a single tab stop, in every configuration, with one tab stop per zone, and the tab order follows the visual order: header → body → footer → next element on the page. When focus reaches an off-screen (virtualized) row or column, the grid scrolls it into view. | +| **2.4.3** Focus Order | Supports | `Tab` and `Shift`+`Tab` move through the header, body and footer cells in visual order, header → body → footer, and then leave the grid for the next element on the page; a roving tabindex keeps one body cell in the page tab sequence. The arrow keys move within and between the zones. When focus reaches an off-screen (virtualized) row or column, the grid scrolls it into view. | | **2.4.4** Link Purpose (In Context) | Not Applicable | The widget's own interface contains no links. Links placed in cells through `htmlEnable` or a column template are rendered as authored, so their text and purpose belong to the host application. | | **2.5.1** Pointer Gestures | Supports | No operation requires a multipoint or path-based gesture; touch handling reads a single touch point, and every drag operation has a non-drag alternative (see 2.5.7). | | **2.5.2** Pointer Cancellation | Supports | The primary actions complete on the up-event: sorting, cell and row selection, and tree expand/collapse are all bound to `click`, not to `mousedown`. | @@ -84,8 +84,8 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **3.3.1** Error Identification | Supports | Input errors are identified to the user: the numeric editor validates against the column's `min`/`max` bounds and marks an out-of-range value, and input that does not fit the column type is rejected at entry. Application-level validation and its error text are owned by the host application, through the cancelable `beforeEditEnd` event. | | **3.3.2** Labels or Instructions | Supports | In-place editors and header/footer filters take an accessible name from the column header text, so the user hears which column is being edited or filtered - for example `Filter ` and `Filter by date: `. | | **3.3.7** Redundant Entry (2.2 only) | Not Applicable | The product contains no multi-step process that re-requests previously entered information. | -| **4.1.1** Parsing | Supports | Markup is generated through a virtual-DOM layer and element ids are machine-generated, so tags are well formed and attributes are not duplicated. | -| **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). `aria-sort` is present only on the headers of sortable columns, so no column reports a sort state it cannot have. | +| **4.1.1** Parsing | Supports | Per the September 2023 errata, this criterion is always supported for WCAG 2.0/2.1, EN 301 549 and Revised 508. It is obsolete/removed in WCAG 2.2. | +| **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, named through the `ariaLabel` property, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). `aria-sort` is present only on the headers of sortable columns, so no column reports a sort state it cannot have. | ### Table 2: Success Criteria, Level AA @@ -99,14 +99,14 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **1.4.4** Resize Text | Supports | Text can be resized to 200% without loss of content or functionality; the header, body and footer scale with it and the grid does not break. Typography and row geometry are token-driven (`--dhx-font-size-normal`, `--dhx-line-height-normal`, `--dhx-step-size`, `rowHeight`), and the contrast themes ship a 16px base. | | **1.4.5** Images of Text | Supports | All text is rendered as real text. Icons are font glyphs; the product uses no raster images. | | **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. As a data grid, two-dimensional layout is intrinsic to the content, for which the criterion permits two-dimensional scrolling. | -| **1.4.11** Non-text Contrast | Supports | With a cell or range selection module, the active cell is marked by a 2px solid border in the theme primary colour - about 3.7:1 against the default light background and about 6.3:1 on `contrast-dark`, both above the 3:1 requirement. Header and footer cells add a 2px focus ring in the same colour. The contrast themes also raise the cell border contrast. | +| **1.4.11** Non-text Contrast | Supports | With a cell or range selection module (`selection: "cell"` / `"complex"`, `blockSelection`), the active cell is marked by a 2px solid border in the theme primary colour: about 7.7:1 on `contrast-light` and 5.3:1 on `contrast-dark` against the cell background (3.9:1 on the default light theme). Header and footer cells show a 2px focus ring in the same colour: about 7.2:1 on `contrast-light` and 6.3:1 on `contrast-dark` against the header background. With `selection: "row"`, and in a grid without a selection module, the focused cell is marked by a 1px dashed outline: about 5.6:1 on `contrast-light` and 5.9:1 on `contrast-dark` against the cell background, and 4.9:1 and 4.7:1 over a selected row. All values are above the 3:1 requirement. The contrast themes also raise the cell border contrast. | | **1.4.12** Text Spacing | Supports | Applying WCAG text-spacing overrides does not clip or overlap text in grid cells, column headers or footer summaries. | | **1.4.13** Content on Hover or Focus | Supports | Overflow tooltips are hoverable, persistent, and dismissible with `Esc` without moving the pointer. Their content repeats the cell's own text, so no information is available on hover alone, and they can be turned off entirely with `tooltip: false`. | | **2.4.5** Multiple Ways | Not Applicable | Applies to locating pages within a set of web pages; out of scope for a single embedded component (host-application concern). | -| **2.4.6** Headings and Labels | Supports | Column headers carry descriptive accessible names, and every filter and in-place editor exposes an accessible name derived from its column. | +| **2.4.6** Headings and Labels | Supports | The grid is named through the `ariaLabel` property, so several grids on one page can be told apart. Column headers carry descriptive accessible names, and every filter and in-place editor exposes an accessible name derived from its column. | | **2.4.7** Focus Visible | Supports | With a cell or range selection module, the active cell carries a persistent 2px solid border drawn in the theme primary colour, and no second outline is drawn over it. With row selection, and in a grid without a selection module, the focused cell carries a dashed focus outline. The keyboard focus location is therefore visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | | **2.4.11** Focus Not Obscured (Minimum) (2.2 only) | Supports | A focused cell is never left behind a frozen zone: when the grid scrolls a cell into view it compensates for the left, right, top and bottom splits and for the header and footer height. | -| **2.5.7** Dragging Movements (2.2 only) | Supports | Every drag operation has a non-drag alternative: range selection through `Shift`+arrows, `Ctrl`+`Shift`+arrows, `Shift`+`Home`/`End` and `Shift`+`Page Up`/`Page Down`; column sizing through `adjust` / `adjustColumnWidth`; and column and row order through the corresponding API. | +| **2.5.7** Dragging Movements (2.2 only) | Supports | Every drag operation has a non-drag alternative: range selection through `Shift`+arrows, `Ctrl`+`Shift`+arrows, `Shift`+`Home`/`End` and `Shift`+`Page Up`/`Page Down`. For column sizing and column and row order, non-drag alternatives are provided by the integrator through the public API (`setColumns()`, `adjustColumnWidth()`, `data.move()`), bound to controls or custom shortcuts; this requires a small customization. | | **2.5.8** Target Size (Minimum) (2.2 only) | Supports | Interactive targets meet 24x24 CSS px: the sort target is the whole header cell (40px tall by the column width), the boolean cell's hit area is the whole cell, and the column resizer grip and the range fill handle carry an enlarged hit area (visual size unchanged). | | **3.1.2** Language of Parts | Not Applicable | The widget does not mix languages within its content; language is host-owned. | | **3.2.3** Consistent Navigation | Not Applicable | Applies across a set of web pages; host-application concern. | diff --git a/docs/grid/api/api_overview.md b/docs/grid/api/api_overview.md index 435a1d391..c5e195ef5 100644 --- a/docs/grid/api/api_overview.md +++ b/docs/grid/api/api_overview.md @@ -183,6 +183,7 @@ description: You can explore the API of Grid in the documentation of the DHTMLX | Name | Description | | ------------------------------------------- | -------------------------------------------------- | | [](grid/api/grid_adjust_config.md) | @getshort(grid/api/grid_adjust_config.md) | +| [](grid/api/grid_arialabel_config.md) | @getshort(grid/api/grid_arialabel_config.md) | | [](grid/api/grid_autoemptyrow_config.md) | @getshort(grid/api/grid_autoemptyrow_config.md) | | [](grid/api/grid_autoheight_config.md) | @getshort(grid/api/grid_autoheight_config.md) | | [](grid/api/grid_autowidth_config.md) | @getshort(grid/api/grid_autowidth_config.md) | diff --git a/docs/grid/api/grid_arialabel_config.md b/docs/grid/api/grid_arialabel_config.md new file mode 100644 index 000000000..c15982ab0 --- /dev/null +++ b/docs/grid/api/grid_arialabel_config.md @@ -0,0 +1,29 @@ +--- +sidebar_label: ariaLabel +title: JavaScript Grid - ariaLabel Config +description: You can explore the ariaLabel config of Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +--- + +# ariaLabel + +@short: Optional. Sets the accessible name of the grid, which a screen reader announces when focus enters the grid + +@signature: {'ariaLabel?: string;'} + +@example: +const grid = new dhx.Grid("grid_container", { + columns: [ + // columns config + ], + data: dataset, + ariaLabel: "Orders" +}); + +@descr: +The value is rendered as the `aria-label` attribute of the element with the `grid` role (the `treegrid` role in the TreeGrid mode). Use the property to tell several grids on one page apart. If the property is not set, the grid has no accessible name. + +An `aria-label` attribute set on the container of the grid does not name the grid, because the element with the `grid` role is nested inside the container. + +**Related articles**: [Accessibility in DHTMLX Grid](grid/accessibility.md) + +@changelog: added in v9.4 diff --git a/docs/grid/configuration.md b/docs/grid/configuration.md index 8918b7dcb..6c8bdd35b 100644 --- a/docs/grid/configuration.md +++ b/docs/grid/configuration.md @@ -3170,7 +3170,7 @@ DHTMLX Grid provides the keyboard navigation that will help you manipulate your ### Default shortcut keys -Keyboard navigation is enabled by default and does not require any selection module. The whole cell surface is a single tab stop: Tab enters Grid, the keys below move the active cell and scroll it into view, and Tab leaves Grid at its first or last cell. Without a selection module, moving the active cell does not select it and does not fire selection events. +Keyboard navigation is enabled by default and does not require any selection module. Tab enters Grid at the header, then Tab and Shift+Tab move cell by cell through the header, the body and the footer. Tab on the last cell of Grid and Shift+Tab on the first header cell move the focus out of Grid. The keys below move the active cell and scroll it into view; the arrow keys also move the focus between the header, the body and the footer. Without a selection module, moving the active cell does not select it and does not fire selection events. The navigation shortcut keys and keys combinations that Grid enables by default are provided below: @@ -3178,11 +3178,11 @@ The navigation shortcut keys and keys combinations that Grid enables by default ArrowUp - moves the active cell to the previous vertical cell + moves the active cell to the previous vertical cell; from the first row of the body, moves the focus to the header ArrowDown - moves the active cell to the next vertical cell + moves the active cell to the next vertical cell; from the last row of the body, moves the focus to the footer (if any) ArrowLeft @@ -3234,11 +3234,11 @@ The navigation shortcut keys and keys combinations that Grid enables by default Tab - moves the active cell to the next horizontal cell or the first cell of the next row; from the last cell of Grid, moves the focus out of Grid + moves the active cell to the next horizontal cell or the first cell of the next row; from the last cell of the body, moves the focus to the footer, or out of Grid when there is no footer Shift+Tab - moves the active cell to the previous horizontal cell or the last cell of the previous row; from the first cell of Grid, moves the focus out of Grid + moves the active cell to the previous horizontal cell or the last cell of the previous row; from the first cell of the body, moves the focus to the header Ctrl+Enter @@ -3267,13 +3267,14 @@ For the accessibility aspects of keyboard navigation, see the [Grid accessibilit In case you want the default shortcut keys to move the selection together with the active cell, you need to specify the [`selection`](grid/api/grid_selection_config.md) property for Grid. -~~~jsx {6} +~~~jsx {6-7} const grid = new dhx.Grid("grid_container", { columns: [ // columns config ], data: dataset, selection: "complex", + multiselection: true, // enables the Shift+Arrow combinations keyNavigation: true // true - by default }); ~~~ @@ -3282,7 +3283,7 @@ const grid = new dhx.Grid("grid_container", { With the `selection` property specified, the arrow keys, their combinations with Ctrl, Home/End, PageUp/PageDown and Tab/Shift+Tab listed in the [Default shortcut keys](#default-shortcut-keys) section move the selection between cells. -The combinations of the shortcut keys listed below do not work when the `selection` property is set to *"complex"*. Use another mode (*"cell" or "row"*) in case you want to activate these navigation keys: +The combinations of the shortcut keys listed below extend the selection and work only when the [`multiselection`](grid/api/grid_multiselection_config.md) property is enabled. Without it, they move the active cell as the plain arrow keys do: @@ -3325,13 +3326,12 @@ The combinations of the shortcut keys listed below do not work when the `selecti It is possible to use shortcut keys for editing a cell in Grid by setting the [`editable:true`](grid/api/grid_editable_config.md) property in the configuration object of Grid. No selection module is required: the editor opens in the active cell. -~~~jsx {7} +~~~jsx {6} const grid = new dhx.Grid("grid_container", { columns: [ // columns config ], data: dataset, - selection: "complex", editable: true, keyNavigation: true // true - by default }); @@ -3345,11 +3345,19 @@ The list of the shortcut keys for editing is given below: - + + + + + + + + + - + @@ -3363,7 +3371,7 @@ The list of the shortcut keys for editing is given below: If you need to use the keyboard navigation for selecting ranges of cells via the user interface, you should enable the [`BlockSelection` module](grid/usage_blockselection.md) in the Grid configuration. :::note -Keyboard navigation works in both the `"range"` and `"manual"` modes. In the `"manual"` mode, applying the selection (e.g., after `Enter`) requires handling via the events, such as [`beforeBlockSelectionApply`](grid/api/blockselection/beforeblockselectionapply_event.md) and [`afterBlockSelectionApply`](grid/api/blockselection/afterblockselectionapply_event.md). +Keyboard navigation for ranges works in the `"range"` mode. ::: The module supports keyboard navigation for selecting and managing ranges, similar to keyboard navigation used in Google Spreadsheets. The following shortcut keys and their combinations are available: diff --git a/docs/grid/localization.md b/docs/grid/localization.md index 1c3609fcc..99b7b00cf 100644 --- a/docs/grid/localization.md +++ b/docs/grid/localization.md @@ -36,8 +36,6 @@ const en = { aria_collapseRow: "Collapse row", aria_subRow: "Details for row {id}", aria_editContent: "Edit content", - aria_enterGrid: "Enter grid", - aria_exitGrid: "Exit grid", aria_dragPanel: "Drag panel", aria_panelHeader: "Panel header", aria_panelFooter: "Panel footer", @@ -148,8 +146,6 @@ The Grid gives its interactive controls accessible names, which screen readers r | `aria_expandRow` | `Expand row` | Expand toggle of a tree branch or a sub-row | | `aria_collapseRow` | `Collapse row` | Collapse toggle of a tree branch or a sub-row | | `aria_editContent` | `Edit content` | Content wrapper of an `htmlEnable` cell | -| `aria_enterGrid` | `Enter grid` | Focus sentinel before the header | -| `aria_exitGrid` | `Exit grid` | Focus sentinel after the footer | | `aria_dragPanel` | `Drag panel` | The drag panel region | | `aria_panelHeader` | `Panel header` | Drag panel header | | `aria_panelFooter` | `Panel footer` | Drag panel footer | @@ -169,8 +165,6 @@ dhx.i18n.setLocale("grid", { aria_expandRow: "Zeile aufklappen", aria_collapseRow: "Zeile zuklappen", aria_editContent: "Inhalt bearbeiten", - aria_enterGrid: "Tabelle betreten", - aria_exitGrid: "Tabelle verlassen", }); const grid = new dhx.Grid("grid_container", { columns, data }); diff --git a/docs/grid/usage_blockselection.md b/docs/grid/usage_blockselection.md index 2450a139c..0b695ac92 100644 --- a/docs/grid/usage_blockselection.md +++ b/docs/grid/usage_blockselection.md @@ -122,7 +122,7 @@ The main points of cell selection while using the `BlockSelection` module are gi ### Keyboard navigation :::note -Keyboard navigation works in both the `"range"` and `"manual"` modes. In the `"manual"` mode, applying the selection (e.g., after `Enter`) requires handling via the events, such as [`beforeBlockSelectionApply`](grid/api/blockselection/beforeblockselectionapply_event.md) and [`afterBlockSelectionApply`](grid/api/blockselection/afterblockselectionapply_event.md). +Keyboard navigation for ranges works in the `"range"` mode. ::: The module supports keyboard navigation for selecting and managing ranges, similar to keyboard navigation used in Google Spreadsheets. The following shortcut keys and their combinations are available: diff --git a/docs/whatsnew.md b/docs/whatsnew.md index 33681930f..4cde0b37a 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -47,6 +47,8 @@ Released on October , 2026 - service properties of a group header row: `$count`, `$totalCount`, `$by` - The ability to [define the options of a header/footer filter manually](grid/configuration.md#custom-options-of-headerfooter-filters) instead of building them from the column data - new property of the `filterConfig` object of **selectFilter** and **comboFilter**: `options` +- The ability to [give a grid an accessible name](grid/accessibility.md#host-page-responsibilities), so that a screen reader can tell several grids on one page apart + - new Grid configuration property: [`ariaLabel`](grid/api/grid_arialabel_config.md) #### Layout @@ -70,7 +72,7 @@ Released on October , 2026 - Colorpicker/Message/Toolbar/Window. The accessible names generated by the widgets are stored in the locale and can be [translated](common_features/accessibility_support.md#localization-of-screen-reader-strings). - Grid. Keyboard navigation: - - The cell surface is a single tab stop, and the arrow keys move between cells whether or not a selection module is enabled. + - Tab and Shift+Tab move cell by cell through the header, the body and the footer and leave the grid at its ends, and the arrow keys move between cells whether or not a selection module is enabled. - Grid. Screen reader announcements: - The new [`announce()`](grid/api/grid_announce_method.md) method sends a message to the live region of the grid, so a screen reader announces a status change without moving focus. diff --git a/sidebars.js b/sidebars.js index 55e4dbd1e..22cec0b12 100644 --- a/sidebars.js +++ b/sidebars.js @@ -2614,6 +2614,7 @@ module.exports = { },*/ items: [ "grid/api/grid_adjust_config", + "grid/api/grid_arialabel_config", "grid/api/grid_autoemptyrow_config", "grid/api/grid_autoheight_config", "grid/api/grid_autowidth_config", From ddb40732c8515a45de305b5cef7b73cb18b3003f Mon Sep 17 00:00:00 2001 From: osorokin Date: Tue, 6 Oct 2026 16:24:56 +0300 Subject: [PATCH 06/10] [dev] Fixed theme links (DHX-5290) --- docs/grid/accessibility.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index 2b4206013..d11c08de9 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -61,7 +61,7 @@ const grid = new dhx.Grid("grid_container", { 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](themes/themes.md) for all the options. +The theme can also be applied through the `data-dhx-theme` attribute of the container or of the root element; see the [Themes overview](/themes/) for all the options. Add a selection module only when users need to select cells or ranges; see the next section. @@ -344,7 +344,7 @@ Navigation is **span-aware**: movement across merged (colspan/rowspan) header an ### High-contrast themes {#highcontrastthemes} -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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](themes/themes.md) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. +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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) 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: @@ -390,7 +390,7 @@ Accessibility is validated continuously, and against the component source rather - [ariaLabel](grid/api/grid_arialabel_config.md) - [Localization](grid/localization.md) - [TreeGrid mode](grid/treegrid_mode.md) -- [Themes overview](themes/themes.md) +- [Themes overview](/themes/) - [Accessibility support across DHTMLX Suite](common_features/accessibility_support.md) - [WCAG 2.2](https://www.w3.org/TR/WCAG22/) - [WAI-ARIA Authoring Practices: Grid / Treegrid](https://www.w3.org/WAI/ARIA/apg/patterns/) From bc1b71045f83155e659c81f91f487f51d7591f96 Mon Sep 17 00:00:00 2001 From: Masha_Rudenko Date: Wed, 7 Oct 2026 13:22:01 +0300 Subject: [PATCH 07/10] [update] accessibility docs revision, ariaLabel pages - new ariaLabel API pages for Toolbar and Window, listed in the API overviews and the sidebar; Grid ariaLabel and keyNavigation pages moved to Usage/Example headings, keyNavigation changelog as a list - accessibility support guide: sentence-case headings, links to the ariaLabel, title and announce() pages, locale examples as a list, palette options wording, varied screenshot descriptions - Grid accessibility guide, conformance report and localization: "color" spelling (EN 301 549 clause title kept), reworded remarks, links to the translatable screen-reader names, keyboard navigation intro split into paragraphs, backticked API links - unique meta descriptions; swatch definition in Colorpicker configuration; dashes and screen-reader wording made consistent --- docs/colorpicker/configuration.md | 2 +- docs/common_features/accessibility_support.md | 43 +++++++++++-------- docs/grid/accessibility.md | 34 ++++++++------- docs/grid/accessibility_conformance_report.md | 42 +++++++++--------- docs/grid/api/grid_arialabel_config.md | 13 ++++-- docs/grid/api/grid_keynavigation_config.md | 25 ++++++++--- docs/grid/localization.md | 2 +- docs/toolbar/api/api_overview.md | 1 + docs/toolbar/api/toolbar_arialabel_config.md | 33 ++++++++++++++ docs/window/api/api_overview.md | 1 + docs/window/api/window_arialabel_config.md | 43 +++++++++++++++++++ sidebars.js | 2 + 12 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 docs/toolbar/api/toolbar_arialabel_config.md create mode 100644 docs/window/api/window_arialabel_config.md diff --git a/docs/colorpicker/configuration.md b/docs/colorpicker/configuration.md index 4a07a729c..d2d7f1c16 100644 --- a/docs/colorpicker/configuration.md +++ b/docs/colorpicker/configuration.md @@ -8,7 +8,7 @@ description: You can explore the configuration of Colorpicker in the documentati ## Custom colors in palette -The palette of Colorpicker contains a set of default colors. They are enumerated below: +The palette of Colorpicker contains a set of default colors. Each color is shown as a swatch, a single color cell of the palette. The default colors are enumerated below: ~~~js const palette = [ diff --git a/docs/common_features/accessibility_support.md b/docs/common_features/accessibility_support.md index 1876b2cce..164523bee 100644 --- a/docs/common_features/accessibility_support.md +++ b/docs/common_features/accessibility_support.md @@ -1,14 +1,14 @@ --- sidebar_label: Accessibility support title: JavaScript Guides - Accessibility Support -description: You can learn about Accessibility support in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +description: Learn how DHTMLX Suite widgets support accessibility, from WAI-ARIA roles and keyboard navigation in Grid, Toolbar, Menu, Sidebar, Ribbon and Colorpicker to translatable screen-reader strings and high-contrast themes. --- # Accessibility support Accessibility is one of the key concepts in present-day web development. It presupposes equal access to web resources for people with diverse abilities. -## Web Conformance Guidelines +## Web conformance guidelines Even if you are not mandated to conform to any particular accessibility standard, it can be helpful to understand the guidelines outlined as they are generally good practices worth incorporating into your web based applications. @@ -22,12 +22,12 @@ Currently the most commonly encountered conformance guidelines and standards are The DHTMLX Suite library meets the main requirements of the WCAG standards and provides support for the following accessibility components: -- [WAI-ARIA Attributes](#wai-aria-attributes) -- [Keyboard Navigation](#keyboard-navigation) +- [WAI-ARIA attributes](#wai-aria-attributes) +- [Keyboard navigation](#keyboard-navigation) - [Localization of screen-reader strings](#localization-of-screen-reader-strings) -- [High-Сontrast Themes](#high-contrast-themes) +- [High-contrast themes](#high-contrast-themes) -## WAI-ARIA Attributes +## WAI-ARIA attributes There are special attributes used in the markup of DHTMLX Suite widgets that make it possible for screen readers to recognize and interpret them to the user, namely: @@ -38,14 +38,14 @@ There are **roles** and **attributes** for elements of grid, sorting, filters, e You can find the following **roles** and **attributes** in the DOM: - role: *grid* (or *treegrid* in the `type: "tree"` mode), *rowgroup*, *row*, *columnheader*, *gridcell*, *button* (the sort control and the tree expand/collapse toggle) -- aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort* (on sortable columns), *aria-selected*, *aria-readonly*, *aria-multiselectable*, *aria-colspan* and *aria-rowspan* (on merged cells), and — for tree rows — *aria-level* and *aria-expanded*. +- aria attributes: *label*, *rowcount*, *colcount*, *rowindex*, *colindex*, *aria-sort* (on sortable columns), *aria-selected*, *aria-readonly*, *aria-multiselectable*, *aria-colspan* and *aria-rowspan* (on merged cells), and *aria-level* and *aria-expanded* (on tree rows). -The Tab and Shift+Tab keys move cell by cell through the header, the body and the footer and leave the grid at its ends. The arrow keys move the active cell within and between these zones, whether or not a selection module is enabled. +The Tab and Shift + Tab keys move cell by cell through the header, the body and the footer and leave the grid at its ends. The arrow keys move the active cell within and between these zones, whether or not a selection module is enabled. -In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. Dynamic changes - sorting, filtering, data loading - are read out through a visually hidden polite live region, which the application can write to itself; all the built-in screen-reader strings are stored in the `aria_*` locale keys and can be translated. +The accessible name of the grid is set with the [`ariaLabel`](grid/api/grid_arialabel_config.md) configuration option. In-place editors and header/footer filters get an accessible name derived from the column header text. Role presentation and aria-hidden are used to hide redundant content (resizers, sort icons, drag ghosts, the selection overlay) from the accessibility tree. Dynamic changes, such as sorting, filtering and data loading, are read out through a visually hidden polite live region, which the application can write to itself with the [`announce()`](grid/api/grid_announce_method.md) method; all the built-in screen-reader strings are stored in the `aria_*` locale keys and can be translated. :::info -For the complete picture — the ARIA model, the keyboard zones (header/body/footer), the focus model, and configuration recipes — see the dedicated [Grid accessibility](grid/accessibility.md) guide. Criterion-by-criterion conformance with WCAG 2.2 AA, Section 508 and EN 301 549 is documented in the [Accessibility Conformance Report](grid/accessibility_conformance_report.md). +For the complete picture, including the ARIA model, the keyboard zones (header/body/footer), the focus model, and configuration recipes, see the dedicated [Grid accessibility](grid/accessibility.md) guide. Criterion-by-criterion conformance with WCAG 2.2 AA, Section 508 and EN 301 549 is documented in the [Accessibility Conformance Report](grid/accessibility_conformance_report.md). ::: ### Chart @@ -80,7 +80,7 @@ A few details worth knowing: - a button built from custom HTML takes its accessible name from its `tooltip`, so give icon-only custom buttons a tooltip; - the toolbar is not a navigation landmark. -The accessible name of a Toolbar is set with the `ariaLabel` configuration option. Set it whenever a page has more than one toolbar, so that users can tell them apart. Without it, the name falls back to the `aria_toolbar` locale string (`"Toolbar"`): +The accessible name of a Toolbar is set with the [`ariaLabel`](toolbar/api/toolbar_arialabel_config.md) configuration option. Set it whenever a page has more than one toolbar, so that users can tell them apart. Without it, the name falls back to the `aria_toolbar` locale string (`"Toolbar"`): ~~~jsx const formatting = new dhx.Toolbar("formatting", { @@ -93,7 +93,7 @@ Role presentation and aria-hidden are used to hide redundant markup from the acc ### Window -A window is exposed with `role="dialog"`. Its accessible name is taken from the `ariaLabel` configuration option, then from `title`, and, when there is neither, from the `aria_dialog` locale string (`"Dialog"`). Use `ariaLabel` for a window without a visible title: +A window is exposed with `role="dialog"`. Its accessible name is taken from the [`ariaLabel`](window/api/window_arialabel_config.md) configuration option, then from [`title`](window/api/window_title_config.md), and, when there is neither, from the `aria_dialog` locale string (`"Dialog"`). Use `ariaLabel` for a window without a visible title: ~~~jsx const search = new dhx.Window({ @@ -107,7 +107,7 @@ const search = new dhx.Window({ ### Colorpicker -The color palette is exposed as a list of options. You can find the following **roles** and **attributes** in the DOM: +The color palette is exposed as a list of options, one per swatch (a single color cell of the palette) and one for the "Add new color" control. You can find the following **roles** and **attributes** in the DOM: - role: *listbox* (the palette), *option* (each swatch and the "Add new color" control), *group* (the custom colors) - aria attributes: *label*, *labelledby*, *selected*. @@ -118,11 +118,11 @@ Each swatch is named by its color name, for example "dark green", rather than by The buttons of alert and confirmation dialogs take their accessible names from the `message` locale: `aria_confirm`, `aria_reject` and `aria_apply`. -## Keyboard Navigation +## Keyboard navigation All DHTMLX Suite widgets are provided with a keyboard navigation support. It allows using a Suite-based app without a mouse pointer. Basic rules include: -- the Tab key is used to navigate between widgets and clickable areas of the widgets. A group of controls - a toolbar, a menu bar, a sidebar, a ribbon, a color palette - is a single tab stop. In a grid, Tab moves cell by cell, and leaves the grid at its ends +- the Tab key is used to navigate between widgets and clickable areas of the widgets. A group of controls, such as a toolbar, a menu bar, a sidebar, a ribbon or a color palette, is a single tab stop. In a grid, Tab moves cell by cell, and leaves the grid at its ends - the Arrow keys are used to move selection or change active elements within widgets - the Esc key closes windows, menus and editors - the Enter is used to open and hide drop-down lists of select controls @@ -180,7 +180,7 @@ In a Colorpicker palette, the focused swatch and the selected one are marked dif ![Colorpicker navigated with the keyboard](/img/colorpicker/colorpicker_keyboard_navigation.png) :::info -For the full list of built-in hotkeys, refer to the **Keyboard Navigation** articles of the following widgets: +For the full list of built-in hotkeys, refer to the **Keyboard navigation** articles of the following widgets: - [Grid](grid/configuration.md#keyboard-navigation) and the [Grid accessibility](grid/accessibility.md#keyboard-navigation) guide - [List](list/configuration.md#arrow-keys-navigation) - [Tree](tree/configuration.md#key-navigation) @@ -189,7 +189,14 @@ For the full list of built-in hotkeys, refer to the **Keyboard Navigation** arti ## Localization of screen-reader strings -The accessible names and announcements that the widgets generate are stored in the locale of each widget as keys with the `aria_` prefix, for example `aria_toolbar` in the `toolbar` locale, `aria_dialog` in the `window` locale, `aria_palette` and the color names in the `colorpicker` locale, and `aria_confirm` in the `message` locale. Translate them with the `dhx.i18n.setLocale()` method before the widget is initialized: +The accessible names and announcements that the widgets generate are stored in the locale of each widget as keys with the `aria_` prefix. For example: + +- `aria_toolbar` in the `toolbar` locale +- `aria_dialog` in the `window` locale +- `aria_palette` and the color names in the `colorpicker` locale +- `aria_confirm` in the `message` locale + +Translate them with the `dhx.i18n.setLocale()` method before the widget is initialized: ~~~jsx dhx.i18n.setLocale("toolbar", { @@ -206,7 +213,7 @@ dhx.i18n.setLocale("colorpicker", { The locale keys are flat: pass each key at the top level of the object, not inside a nested object. -## High-Contrast Themes +## High-contrast themes For users that are visually impaired due to color deficiencies, care should be taken when using colors to provide information. diff --git a/docs/grid/accessibility.md b/docs/grid/accessibility.md index d11c08de9..0433057d9 100644 --- a/docs/grid/accessibility.md +++ b/docs/grid/accessibility.md @@ -1,7 +1,7 @@ --- sidebar_label: Accessibility title: JavaScript Grid - Accessibility -description: You can learn about accessibility and keyboard navigation in DHTMLX Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +description: Make DHTMLX Grid accessible to keyboard and screen-reader users. Learn about the WAI-ARIA roles, keyboard navigation with and without selection, screen-reader announcements and high-contrast themes that target WCAG 2.2 AA in DHTMLX Suite. --- # Accessibility in DHTMLX Grid @@ -23,7 +23,7 @@ Conformance is reported criterion by criterion rather than as a blanket claim. T | Focus model | Tab / 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 model | Optional: single-cell/row (`selection`) or spreadsheet-style range (`blockSelection`), for selecting cells and ranges | | Screen readers | Tested with NVDA, JAWS and VoiceOver | -| Visual accessibility | Dedicated light and dark high-contrast themes (colour-blind friendly, AA contrast, 16px base) and non-colour cues | +| Visual accessibility | Dedicated light and dark high-contrast themes (color-blind friendly, AA contrast, 16px base) and non-color cues | | Text resize / zoom | Layout remains operable up to 400% zoom / text spacing overrides | | Customization | Public `announce()` API for dynamic messages, and localizable `aria_*` strings for every built-in screen-reader description | | Standards | WCAG 2.2 AA, Section 508, EN 301 549 | @@ -150,7 +150,7 @@ const grid = new dhx.Grid("grid_container", { The sections below describe each capability in detail. -## WAI-ARIA Attributes +## 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. @@ -215,7 +215,7 @@ In-place editor inputs and header/footer filters receive an accessible name deri 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 +## Screen-reader support DHTMLX Grid is tested against the most widely used assistive technologies: @@ -262,7 +262,7 @@ The Grid already announces the following out of the box: ### Translatable screen-reader names -The strings the screen reader hears live in the Grid locale, so they translate with the rest of the UI. They fall into two groups: +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. @@ -282,7 +282,11 @@ Placeholders in curly braces (`{column}`, `{count}`, `{min}`, `{max}`, `{value}` ## Keyboard navigation -Keyboard navigation is on by default (`keyNavigation: true`); set `keyNavigation: false` to opt out. It works in every configuration: without a selection module the keys move the active cell, and with one they move the selection as well; see [Keyboard navigation with and without selection](#selectionmodules). 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. +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](#selectionmodules). + +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: @@ -340,11 +344,11 @@ Shortcuts are organized into **zones** (body, header, footer) and resolved by wh 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 colour +## Low vision and color ### High-contrast themes {#highcontrastthemes} -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 colour-blind friendly, and raise the base font size to 16px. See the [Themes](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) pages for details. +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](/themes/) guide and the [Light High Contrast](themes/contrast_light_theme.md) / [Dark High Contrast](themes/contrast_dark_theme.md) 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: @@ -356,10 +360,10 @@ The `contrast-dark` theme draws light text and borders on a dark background. Whe ### Other low-vision support -- **Colour 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`. +- **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 colour, 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. +- **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 @@ -383,11 +387,11 @@ Accessibility is validated continuously, and against the component source rather - [Accessibility Conformance Report (VPAT 2.5)](grid/accessibility_conformance_report.md) - [Keyboard navigation](grid/configuration.md#keyboard-navigation) -- [keyNavigation](grid/api/grid_keynavigation_config.md) -- [selection](grid/api/grid_selection_config.md) -- [blockSelection](grid/api/grid_blockselection_config.md) -- [announce()](grid/api/grid_announce_method.md) -- [ariaLabel](grid/api/grid_arialabel_config.md) +- [`keyNavigation`](grid/api/grid_keynavigation_config.md) +- [`selection`](grid/api/grid_selection_config.md) +- [`blockSelection`](grid/api/grid_blockselection_config.md) +- [`announce()`](grid/api/grid_announce_method.md) +- [`ariaLabel`](grid/api/grid_arialabel_config.md) - [Localization](grid/localization.md) - [TreeGrid mode](grid/treegrid_mode.md) - [Themes overview](/themes/) diff --git a/docs/grid/accessibility_conformance_report.md b/docs/grid/accessibility_conformance_report.md index 76a334c1c..afb54b799 100644 --- a/docs/grid/accessibility_conformance_report.md +++ b/docs/grid/accessibility_conformance_report.md @@ -1,7 +1,7 @@ --- sidebar_label: Conformance report (VPAT) title: JavaScript Grid - Accessibility Conformance Report -description: You can explore the accessibility conformance report (VPAT 2.5) for DHTMLX Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +description: DHTMLX Suite Accessibility Conformance Report, based on VPAT 2.5 - conformance with WCAG 2.2 AA, Section 508, and EN 301 549. --- # Accessibility Conformance Report @@ -18,11 +18,11 @@ This report documents how DHTMLX Grid conforms to the major accessibility standa ## Notes -- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output (always on), and an AA-conformant colour scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. -- DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities - the page title, ``, skip/bypass mechanisms, and site-wide navigation - belong to the host application and are reported as *Not Applicable* with an explanation. +- This report evaluates DHTMLX Grid in its **recommended accessibility configuration**: keyboard navigation enabled (`keyNavigation: true`, the default), WAI-ARIA output (always on), and an AA-conformant color scheme selected through `dhx.setTheme("contrast-light")` / `dhx.setTheme("contrast-dark")` or the equivalent `data-dhx-theme` attribute. Where a conformance level depends on this configuration, the Remarks say so. See the [Accessibility](grid/accessibility.md) guide for setup instructions. +- DHTMLX Grid is **web content** rendered by a host application. Page-level responsibilities (the page title, ``, skip/bypass mechanisms, and site-wide navigation) belong to the host application and are reported as *Not Applicable* with an explanation. - In the WCAG tables, only the **Web** response is populated. The *Electronic Docs*, *Software*, *Closed*, and *Authoring Tool* rows are *Not Applicable*: the product is not standalone (non-web) software, closed functionality, an authoring tool, or a piece of electronic documentation. -**Evaluation Methods Used:** Testing was performed using a combination of automated testing (axe-core in a headless browser), manual code review of the component source and stylesheets (the roles and ARIA attributes emitted per structural part, the keyboard shortcut registry and focus model, the editor and filter naming, and the theme colour tokens, with contrast ratios computed from those tokens for the default, dark, `contrast-light` and `contrast-dark` themes), assistive-technology testing (manual passes with **NVDA + Firefox** and **JAWS + Chrome** on Windows and **VoiceOver + Safari** on macOS), and browser overrides (colour-vision-deficiency emulation, 200% / 400% zoom, and WCAG text-spacing overrides). +**Evaluation Methods Used:** Testing was performed using a combination of automated testing (axe-core in a headless browser), manual code review of the component source and stylesheets (the roles and ARIA attributes emitted per structural part, the keyboard shortcut registry and focus model, the editor and filter naming, and the theme color tokens, with contrast ratios computed from those tokens for the default, dark, `contrast-light` and `contrast-dark` themes), assistive-technology testing (manual passes with **NVDA + Firefox** and **JAWS + Chrome** on Windows and **VoiceOver + Safari** on macOS), and browser overrides (color-vision-deficiency emulation, 200% / 400% zoom, and WCAG text-spacing overrides). ## Applicable standards / guidelines @@ -54,14 +54,14 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| **1.1.1** Non-text Content | Supports | Icons are rendered as font glyphs, not images of text. Interactive icons carry accessible names: the tree expand/collapse toggle is `role="button"` with `aria-label`, and the sort affordance is `role="button"` with `aria-label="Sort by "`. Decorative parts - 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"` or `role="presentation"`. | +| **1.1.1** Non-text Content | Supports | Icons are rendered as font glyphs, not images of text. Interactive icons carry accessible names: the tree expand/collapse toggle is `role="button"` with `aria-label`, and the sort affordance is `role="button"` with `aria-label="Sort by "`. Decorative parts (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"` or `role="presentation"`. | | **1.2.1** Audio-only and Video-only (Prerecorded) | Not Applicable | The product contains no audio or video content. | | **1.2.2** Captions (Prerecorded) | Not Applicable | No prerecorded multimedia. | | **1.2.3** Audio Description or Media Alternative (Prerecorded) | Not Applicable | No prerecorded multimedia. | | **1.3.1** Info and Relationships | Supports | The grid uses `grid` → `rowgroup` → `row` → `gridcell`/`columnheader` roles, and `treegrid` in tree mode; the grid role owns only row groups and rows, and the focus sentinels are placed outside it. Position is exposed with `aria-rowindex`/`aria-colindex` against `aria-rowcount`/`aria-colcount`; the extent of a merged cell with `aria-colspan`/`aria-rowspan`; editability with `aria-readonly`; multi-selection capability with `aria-multiselectable`; sort state with `aria-sort`, on sortable columns only; hierarchy with `aria-level` and `aria-expanded`. ARIA output is always on. | | **1.3.2** Meaningful Sequence | Supports | DOM and reading order follow the visual order. Rows and columns are virtualized, but `aria-rowindex`/`aria-colindex` carry the absolute position in the dataset rather than the position in the DOM, so sequence is preserved for assistive technology. | | **1.3.3** Sensory Characteristics | Supports | No instruction or label relies solely on shape, size, or spatial location; interactive elements carry text or an ARIA name. | -| **1.4.1** Use of Color | Supports | Colour 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`; hierarchy by `aria-level` and `aria-expanded`. The `contrast-light` and `contrast-dark` themes are additionally colour-blind friendly; verified under protanopia / deuteranopia / tritanopia / achromatopsia emulation. | +| **1.4.1** Use of Color | Supports | 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`; hierarchy by `aria-level` and `aria-expanded`. The `contrast-light` and `contrast-dark` themes are additionally color-blind friendly; verified under protanopia / deuteranopia / tritanopia / achromatopsia emulation. | | **1.4.2** Audio Control | Not Applicable | The product produces no auto-playing audio. | | **2.1.1** Keyboard | Supports | Keyboard navigation is on by default (`keyNavigation: true`) and works in every configuration, with or without a selection module. The body, header and footer are each fully operable: arrow keys, `Home`/`End`, `Ctrl`+`Home`/`End`, `Page Up`/`Page Down` and `Tab` move the active cell (and the selection, when a selection module is enabled); `Shift` combinations extend the selection; with `editable`, `Enter`/`F2`/`Space` open editors and toggle boolean cells; `Escape` cancels; `Enter`/`Space` sort from the header; `Ctrl`+`Enter` and the arrow keys expand and collapse tree branches; `Ctrl`+`Z`/`Ctrl`+`Shift`+`Z` undo and redo. Column resizing (`resizable`) and column/row drag-and-drop (`dragItem`) are pointer features; keyboard equivalents are added by the integrator through the public API (`setColumns()`, `adjustColumnWidth()`, `data.move()`) bound to custom shortcuts with the `beforeKeyDown`/`afterKeyDown` events (by design). | | **2.1.2** No Keyboard Trap | Supports | Focus is never trapped. `Tab` past the last cell of the grid and `Shift`+`Tab` before the first header cell move focus out of the widget through hidden focus sentinels, in every configuration, including one with frozen columns (`leftSplit`/`rightSplit`). `Escape` deactivates a header or footer filter control and returns focus to its cell. | @@ -82,7 +82,7 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **3.2.2** On Input | Supports | Changing a value does not change context. Editing a cell or typing in a filter updates the grid in place, which is the documented purpose of those controls. | | **3.2.6** Consistent Help (2.2 only) | Not Applicable | The widget provides no help mechanism; this is a page-level concern. | | **3.3.1** Error Identification | Supports | Input errors are identified to the user: the numeric editor validates against the column's `min`/`max` bounds and marks an out-of-range value, and input that does not fit the column type is rejected at entry. Application-level validation and its error text are owned by the host application, through the cancelable `beforeEditEnd` event. | -| **3.3.2** Labels or Instructions | Supports | In-place editors and header/footer filters take an accessible name from the column header text, so the user hears which column is being edited or filtered - for example `Filter ` and `Filter by date: `. | +| **3.3.2** Labels or Instructions | Supports | In-place editors and header/footer filters take an accessible name from the column header text, so the user hears which column is being edited or filtered, for example `Filter ` and `Filter by date: `. | | **3.3.7** Redundant Entry (2.2 only) | Not Applicable | The product contains no multi-step process that re-requests previously entered information. | | **4.1.1** Parsing | Supports | Per the September 2023 errata, this criterion is always supported for WCAG 2.0/2.1, EN 301 549 and Revised 508. It is obsolete/removed in WCAG 2.2. | | **4.1.2** Name, Role, Value | Supports | Every structural and interactive part exposes a role, its states and an accessible name: the container (`grid`/`treegrid`, named through the `ariaLabel` property, `aria-readonly`, `aria-multiselectable`), rows and cells (`row`, `gridcell`, `aria-selected`, `aria-readonly`), headers (`columnheader`, `aria-sort`), the tree toggle and sort affordance (`button` with `aria-label`), and editors and filters (named from the column header). `aria-sort` is present only on the headers of sortable columns, so no column reports a sort state it cannot have. | @@ -95,31 +95,31 @@ theme" and "requires `keyNavigation`, on by default" are configuration dependenc | **1.2.5** Audio Description (Prerecorded) | Not Applicable | No prerecorded video. | | **1.3.4** Orientation | Supports | Content is not restricted to a single display orientation. | | **1.3.5** Identify Input Purpose | Not Applicable | The product's editors collect application data defined by the integrator's columns, not the user's own personal information covered by the WCAG input-purpose list. | -| **1.4.3** Contrast (Minimum) | Supports | The `contrast-light` and `contrast-dark` themes meet AA out of the box: body text is about 8.5:1 and 13.2:1 against the cell background, secondary and additional text about 7.3:1 and 10.2:1, and both themes raise the base font size to 16px and are colour-blind friendly. The built-in dark theme also meets AA for body text. Meeting the criterion requires selecting one of these themes - one supported method meets it. | +| **1.4.3** Contrast (Minimum) | Supports | The `contrast-light` and `contrast-dark` themes meet AA out of the box: body text is about 8.5:1 and 13.2:1 against the cell background, secondary and additional text about 7.3:1 and 10.2:1, and both themes raise the base font size to 16px and are color-blind friendly. The built-in dark theme also meets AA for body text. Meeting the criterion requires selecting one of these themes, and one supported method meets it. | | **1.4.4** Resize Text | Supports | Text can be resized to 200% without loss of content or functionality; the header, body and footer scale with it and the grid does not break. Typography and row geometry are token-driven (`--dhx-font-size-normal`, `--dhx-line-height-normal`, `--dhx-step-size`, `rowHeight`), and the contrast themes ship a 16px base. | | **1.4.5** Images of Text | Supports | All text is rendered as real text. Icons are font glyphs; the product uses no raster images. | -| **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. As a data grid, two-dimensional layout is intrinsic to the content, for which the criterion permits two-dimensional scrolling. | -| **1.4.11** Non-text Contrast | Supports | With a cell or range selection module (`selection: "cell"` / `"complex"`, `blockSelection`), the active cell is marked by a 2px solid border in the theme primary colour: about 7.7:1 on `contrast-light` and 5.3:1 on `contrast-dark` against the cell background (3.9:1 on the default light theme). Header and footer cells show a 2px focus ring in the same colour: about 7.2:1 on `contrast-light` and 6.3:1 on `contrast-dark` against the header background. With `selection: "row"`, and in a grid without a selection module, the focused cell is marked by a 1px dashed outline: about 5.6:1 on `contrast-light` and 5.9:1 on `contrast-dark` against the cell background, and 4.9:1 and 4.7:1 over a selected row. All values are above the 3:1 requirement. The contrast themes also raise the cell border contrast. | +| **1.4.10** Reflow | Supports | Content scrolls and remains operable at 400% zoom. A two-dimensional layout is intrinsic to tabular data, and the criterion exempts such content from reflow, so the grid scrolls both horizontally and vertically. | +| **1.4.11** Non-text Contrast | Supports | With a cell or range selection module (`selection: "cell"` / `"complex"`, `blockSelection`), the active cell is marked by a 2px solid border in the theme primary color: about 7.7:1 on `contrast-light` and 5.3:1 on `contrast-dark` against the cell background (3.9:1 on the default light theme). Header and footer cells show a 2px focus ring in the same color: about 7.2:1 on `contrast-light` and 6.3:1 on `contrast-dark` against the header background. With `selection: "row"`, and in a grid without a selection module, the focused cell is marked by a 1px dashed outline: about 5.6:1 on `contrast-light` and 5.9:1 on `contrast-dark` against the cell background, and 4.9:1 and 4.7:1 over a selected row. All values are above the 3:1 requirement. The contrast themes also raise the cell border contrast. | | **1.4.12** Text Spacing | Supports | Applying WCAG text-spacing overrides does not clip or overlap text in grid cells, column headers or footer summaries. | | **1.4.13** Content on Hover or Focus | Supports | Overflow tooltips are hoverable, persistent, and dismissible with `Esc` without moving the pointer. Their content repeats the cell's own text, so no information is available on hover alone, and they can be turned off entirely with `tooltip: false`. | | **2.4.5** Multiple Ways | Not Applicable | Applies to locating pages within a set of web pages; out of scope for a single embedded component (host-application concern). | | **2.4.6** Headings and Labels | Supports | The grid is named through the `ariaLabel` property, so several grids on one page can be told apart. Column headers carry descriptive accessible names, and every filter and in-place editor exposes an accessible name derived from its column. | -| **2.4.7** Focus Visible | Supports | With a cell or range selection module, the active cell carries a persistent 2px solid border drawn in the theme primary colour, and no second outline is drawn over it. With row selection, and in a grid without a selection module, the focused cell carries a dashed focus outline. The keyboard focus location is therefore visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | +| **2.4.7** Focus Visible | Supports | With a cell or range selection module, the active cell carries a persistent 2px solid border drawn in the theme primary color, and no second outline is drawn over it. With row selection, and in a grid without a selection module, the focused cell carries a dashed focus outline. The keyboard focus location is therefore visible at all times, including during arrow-key navigation. Header and footer cells show a 2px focus ring, and filter inputs and cell editors an inset ring. | | **2.4.11** Focus Not Obscured (Minimum) (2.2 only) | Supports | A focused cell is never left behind a frozen zone: when the grid scrolls a cell into view it compensates for the left, right, top and bottom splits and for the header and footer height. | | **2.5.7** Dragging Movements (2.2 only) | Supports | Every drag operation has a non-drag alternative: range selection through `Shift`+arrows, `Ctrl`+`Shift`+arrows, `Shift`+`Home`/`End` and `Shift`+`Page Up`/`Page Down`. For column sizing and column and row order, non-drag alternatives are provided by the integrator through the public API (`setColumns()`, `adjustColumnWidth()`, `data.move()`), bound to controls or custom shortcuts; this requires a small customization. | | **2.5.8** Target Size (Minimum) (2.2 only) | Supports | Interactive targets meet 24x24 CSS px: the sort target is the whole header cell (40px tall by the column width), the boolean cell's hit area is the whole cell, and the column resizer grip and the range fill handle carry an enlarged hit area (visual size unchanged). | | **3.1.2** Language of Parts | Not Applicable | The widget does not mix languages within its content; language is host-owned. | | **3.2.3** Consistent Navigation | Not Applicable | Applies across a set of web pages; host-application concern. | -| **3.2.4** Consistent Identification | Supports | Components with the same function are identified consistently across the header, body and footer zones - the same roles, the same naming pattern and the same key bindings. | +| **3.2.4** Consistent Identification | Supports | Components with the same function are identified consistently across the header, body and footer zones: the same roles, the same naming pattern and the same key bindings. | | **3.3.3** Error Suggestion | Supports | Where a correction is known, it is applied: numeric input outside the column's `min`/`max` bounds is corrected to the nearest valid value, so a valid value is always produced. Suggestion text for application-level validation is provided by the host application, through `beforeEditEnd`. | -| **3.3.4** Error Prevention (Legal, Financial, Data) | Supports | Actions that change or remove data are reversible and guarded: the History module provides undo and redo for edits, range clearing and clipboard operations, and every such action can additionally be gated by the host through the cancelable `before*` events - among them `beforeEditEnd`, `beforeKeyDown`, `beforeSort`, `beforeResizeStart`, `beforeRowDrag`/`beforeRowDrop` and `beforeColumnDrag`/`beforeColumnDrop`. | +| **3.3.4** Error Prevention (Legal, Financial, Data) | Supports | Actions that change or remove data are reversible and guarded. The History module provides undo and redo for edits, range clearing and clipboard operations. Every such action can additionally be gated by the host through the cancelable `before*` events, among them `beforeEditEnd`, `beforeKeyDown`, `beforeSort`, `beforeResizeStart`, `beforeRowDrag`/`beforeRowDrop` and `beforeColumnDrag`/`beforeColumnDrop`. | | **3.3.8** Accessible Authentication (Minimum) (2.2 only) | Not Applicable | The product performs no authentication. | -| **4.1.3** Status Messages | Supports | A visually-hidden polite live region (`role="status"`, `aria-live="polite"`, `aria-atomic="true"`) announces dynamic changes without moving focus - sort direction changes, filter results with the matching row count, completed loads, and numeric-editor range corrections. The same region is available to the host application through the public [`announce()`](grid/api/grid_announce_method.md) method, and every built-in message is localizable through the `aria_*` locale keys. State that persists is additionally exposed on the elements themselves: sort direction through `aria-sort`, selection through `aria-selected`, dataset size through `aria-rowcount`/`aria-colcount`, and branch state through `aria-expanded`. | +| **4.1.3** Status Messages | Supports | The Grid uses a visually hidden live region (`role="status"`, `aria-live="polite"`, `aria-atomic="true"`) to announce dynamic changes without moving focus. The announcements cover sort direction changes, filter results with the matching row count, completed loads, and numeric-editor range corrections. The same region is available to the host application through the public [`announce()`](grid/api/grid_announce_method.md) method, and every built-in message is localizable through the `aria_*` locale keys. State that persists is additionally exposed on the elements themselves: sort direction through `aria-sort`, selection through `aria-selected`, dataset size through `aria-rowcount`/`aria-colcount`, and branch state through `aria-expanded`. | ## Revised Section 508 report WCAG-mapped provisions are answered in the WCAG 2.x report above and referenced here. Chapter 4 (Hardware) -is Not Applicable - DHTMLX Grid is a software component with no hardware or physical controls. +is Not Applicable, because DHTMLX Grid is a software component with no hardware or physical controls. ### Chapter 3: Functional Performance Criteria @@ -127,7 +127,7 @@ is Not Applicable - DHTMLX Grid is a software component with no hardware or phys |---|---|---| | 302.1 Without Vision | Supports | Operable with a screen reader through the `grid`/`treegrid` model, the position and state attributes, the accessible names on editors and filters, and the keyboard model. See WCAG 1.3.1, 2.1.1, 4.1.2. | | 302.2 With Limited Vision | Supports | Zoom, text resize and reflow are supported, and AA contrast is met by the `contrast-light` / `contrast-dark` themes, which also raise the base font size to 16px. See WCAG 1.4.3, 1.4.4, 1.4.10, 1.4.11. | -| 302.3 Without Perception of Color | Supports | No information is conveyed by colour alone: sort direction, selection, editability and hierarchy each carry a glyph or an ARIA state alongside the colour, and the `contrast-light` / `contrast-dark` themes are colour-blind friendly. See WCAG 1.4.1. | +| 302.3 Without Perception of Color | Supports | No information is conveyed by color alone: sort direction, selection, editability and hierarchy each carry a glyph or an ARIA state alongside the color, and the `contrast-light` / `contrast-dark` themes are color-blind friendly. See WCAG 1.4.1. | | 302.4 Without Hearing | Supports | No information is conveyed by sound. | | 302.5 With Limited Hearing | Supports | No information is conveyed by sound. | | 302.6 Without Speech | Supports | No speech input is required to operate the product. | @@ -142,13 +142,13 @@ is Not Applicable - DHTMLX Grid is a software component with no hardware or phys | 501.1 Scope - Incorporation of WCAG 2.0 AA | See WCAG 2.x section | See the WCAG 2.x report above. | | 502 Interoperability with Assistive Technology | Not Applicable | Applies to non-web (platform) software. DHTMLX Grid is web content that exposes standard DOM and ARIA to the browser accessibility tree; interoperability is covered by the WCAG results (Clause 9 / 501.1). | | 503 Applications | Not Applicable | Applies to non-web platform software; the product is web content. | -| 504.2 Content Creation or Editing | Not Applicable | DHTMLX Grid is not an authoring tool for web content - it edits tabular application data, not accessibility-relevant web content. | +| 504.2 Content Creation or Editing | Not Applicable | DHTMLX Grid is not an authoring tool for web content: it edits tabular application data, not accessibility-relevant web content. | ### Chapter 6: Support Documentation and Services | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes and the focus model, together with the related configuration, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | +| 602.2 Accessibility and Compatibility Features | Supports | The documentation describes the product's accessibility features: the [Accessibility](grid/accessibility.md) guide covers the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes and the focus model, together with the related configuration, the [`announce()`](grid/api/grid_announce_method.md) API and the [localizable `aria_*` strings](grid/accessibility.md#translatable-screen-reader-names). | | 602.3 Electronic Support Documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA. | | 602.4 Alternate Formats for Non-Electronic Support Documentation | Not Applicable | Documentation is delivered electronically only; there are no non-electronic (print) support documents. | | 603.2 Information on Accessibility and Compatibility Features | Supports | The product's accessibility features are documented publicly in the [Accessibility](grid/accessibility.md) guide and can be provided by the support channel on request. | @@ -167,7 +167,7 @@ These mirror the Section 508 Chapter 3 results (same conclusions). |---|---|---| | 4.2.1 Usage without vision | Supports | See 508 302.1 (screen-reader operable through the grid/treegrid model, position and state attributes, and the keyboard model). | | 4.2.2 Usage with limited vision | Supports | See 508 302.2 (AA contrast and a 16px base through the contrast themes; zoom and reflow supported). | -| 4.2.3 Usage without perception of colour | Supports | See 508 302.3 (glyphs and ARIA state accompany every colour cue; the contrast themes are colour-blind friendly). | +| 4.2.3 Usage without perception of colour | Supports | See 508 302.3 (glyphs and ARIA state accompany every color cue; the contrast themes are color-blind friendly). | | 4.2.4 Usage without hearing | Supports | No information conveyed by sound. | | 4.2.5 Usage with limited hearing | Supports | No information conveyed by sound. | | 4.2.6 Usage with no or limited vocal capability | Supports | No speech input required. | @@ -194,13 +194,13 @@ and 2 above. **Not Applicable (assessed under Clause 9).** DHTMLX Grid is web content evaluated under Clause 9 (Web); it is not non-web software. The WCAG-mapped software provisions (11.1.1.1-11.4.1.3) are covered by the WCAG -2.x results. 11.8.2 (Accessible content creation) is Not Applicable - the product is not an authoring tool. +2.x results. 11.8.2 (Accessible content creation) is Not Applicable, because the product is not an authoring tool. ### Clause 12: Documentation and Support Services | Criteria | Conformance level | Remarks and explanations | |---|---|---| -| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features - the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes, the focus model, the [`announce()`](grid/api/grid_announce_method.md) API and the localizable `aria_*` strings. | +| 12.1.1 Accessibility and compatibility features | Supports | The [Accessibility](grid/accessibility.md) guide documents the product's accessibility features: the WAI-ARIA model, the keyboard zones and shortcuts, keyboard navigation with and without the selection modules, the high-contrast themes, the focus model, the [`announce()`](grid/api/grid_announce_method.md) API and the [localizable `aria_*` strings](grid/accessibility.md#translatable-screen-reader-names). | | 12.1.2 Accessible documentation | Supports (see WCAG 2.x section) | The documentation site is delivered as web content and is maintained to meet WCAG 2.2 AA (see 508 602.3). | | 12.2.2 Information on accessibility and compatibility features | Supports | Accessibility features are documented publicly and can be surfaced by support on request. | | 12.2.3 Effective communication | Supports | Support is provided over a text-based email channel; users interact through their own assistive technology. | diff --git a/docs/grid/api/grid_arialabel_config.md b/docs/grid/api/grid_arialabel_config.md index c15982ab0..fdd41d73c 100644 --- a/docs/grid/api/grid_arialabel_config.md +++ b/docs/grid/api/grid_arialabel_config.md @@ -8,9 +8,15 @@ description: You can explore the ariaLabel config of Grid in the documentation o @short: Optional. Sets the accessible name of the grid, which a screen reader announces when focus enters the grid -@signature: {'ariaLabel?: string;'} +#### Usage -@example: +~~~ts +ariaLabel?: string; +~~~ + +#### Example + +~~~jsx const grid = new dhx.Grid("grid_container", { columns: [ // columns config @@ -18,12 +24,13 @@ const grid = new dhx.Grid("grid_container", { data: dataset, ariaLabel: "Orders" }); +~~~ @descr: The value is rendered as the `aria-label` attribute of the element with the `grid` role (the `treegrid` role in the TreeGrid mode). Use the property to tell several grids on one page apart. If the property is not set, the grid has no accessible name. An `aria-label` attribute set on the container of the grid does not name the grid, because the element with the `grid` role is nested inside the container. -**Related articles**: [Accessibility in DHTMLX Grid](grid/accessibility.md) +**Related articles**: [Grid accessibility](grid/accessibility.md) @changelog: added in v9.4 diff --git a/docs/grid/api/grid_keynavigation_config.md b/docs/grid/api/grid_keynavigation_config.md index c2e615d29..1fd72a7ae 100644 --- a/docs/grid/api/grid_keynavigation_config.md +++ b/docs/grid/api/grid_keynavigation_config.md @@ -1,18 +1,24 @@ --- sidebar_label: keyNavigation title: JavaScript Grid - keyNavigation Config -description: You can explore the keyNavigation config of Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +description: Enable or turn off keyboard navigation in DHTMLX Grid with the keyNavigation property. The navigation keys move the active cell with or without a selection module, and editing from the keyboard requires only the editable config. --- # keyNavigation @short: Optional. Enables keyboard navigation in Grid -@signature: {'keyNavigation?: boolean;'} +#### Usage + +~~~ts +keyNavigation?: boolean; +~~~ @default: true -@example: +#### Example + +~~~jsx const grid = new dhx.Grid("grid_container", { columns: [ // columns config @@ -22,13 +28,20 @@ const grid = new dhx.Grid("grid_container", { editable: true, keyNavigation: false }); +~~~ @descr: **Related sample**: [Grid. Key navigation](https://snippet.dhtmlx.com/y9kdk0md) -Keyboard navigation works without any selection module: the navigation keys move the active cell, but do not select it. Set the [selection](grid/api/grid_selection_config.md) property to move the selection with the keyboard, and set the [editable](grid/api/grid_editable_config.md) property to edit cells from the keyboard. Read the details in the [Key Navigation](grid/configuration.md#keyboard-navigation) article and in the [Grid accessibility](grid/accessibility.md) guide. +Keyboard navigation works without any selection module: the navigation keys move the active cell, but do not select it. Set the [`selection`](grid/api/grid_selection_config.md) property to move the selection with the keyboard, and set the [`editable`](grid/api/grid_editable_config.md) property to edit cells from the keyboard. -@changelog: added in v6.3; the keyboard navigation model was extended in v9.3.5; navigation and editing without a selection module were added in v9.3.12 +**Related articles**: +- [Initialize Grid](grid/initialization.md#initialize-grid) +- [Keyboard navigation](grid/configuration.md#keyboard-navigation) +- [Grid accessibility](grid/accessibility.md) -[comment]: # (@related: grid/initialization.md#initialize-grid grid/configuration.md#keyboard-navigation) +@changelog: +- Navigation and editing without a selection module were added in v9.3.12 +- The keyboard navigation model was extended in v9.3.5 +- Added in v6.3 diff --git a/docs/grid/localization.md b/docs/grid/localization.md index 99b7b00cf..8bddfb272 100644 --- a/docs/grid/localization.md +++ b/docs/grid/localization.md @@ -1,7 +1,7 @@ --- sidebar_label: Localization title: JavaScript Grid - Localization -description: You can explore how to work with Grid in the documentation of the DHTMLX JavaScript UI library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX Suite. +description: Translate DHTMLX Grid with a custom locale, including the screen-reader announcements for sorting, filtering, loading and validation and the accessible names of its controls, and send your own announcements with the announce() method. --- # Localization diff --git a/docs/toolbar/api/api_overview.md b/docs/toolbar/api/api_overview.md index 56344c3f4..559378b2d 100644 --- a/docs/toolbar/api/api_overview.md +++ b/docs/toolbar/api/api_overview.md @@ -52,6 +52,7 @@ Use [TreeCollection events](/tree_collection/#events) to work with data. | Name | Description | | ------------------------------------------------ | ------------------------------------------------------- | +| [](toolbar/api/toolbar_arialabel_config.md) | @getshort(toolbar/api/toolbar_arialabel_config.md) | | [](toolbar/api/toolbar_css_config.md) | @getshort(toolbar/api/toolbar_css_config.md) | | [](toolbar/api/toolbar_data_config.md) | @getshort(toolbar/api/toolbar_data_config.md) | | [](toolbar/api/toolbar_menucss_config.md) | @getshort(toolbar/api/toolbar_menucss_config.md) | diff --git a/docs/toolbar/api/toolbar_arialabel_config.md b/docs/toolbar/api/toolbar_arialabel_config.md new file mode 100644 index 000000000..a5f1dc7e2 --- /dev/null +++ b/docs/toolbar/api/toolbar_arialabel_config.md @@ -0,0 +1,33 @@ +--- +sidebar_label: ariaLabel +title: JavaScript Toolbar - ariaLabel Config +description: Set the accessible name of DHTMLX Toolbar with the ariaLabel property, so that screen-reader users can tell several toolbars on a page apart. Without it, the name comes from the aria_toolbar locale key of DHTMLX Suite. +--- + +# ariaLabel + +@short: Optional. Sets the accessible name of the toolbar, which a screen reader announces when focus enters the toolbar + +#### Usage + +~~~ts +ariaLabel?: string; +~~~ + +#### Example + +~~~jsx +const toolbar = new dhx.Toolbar("toolbar_container", { + ariaLabel: "Formatting", + data: toolbarItems +}); +~~~ + +@descr: +The value is rendered as the `aria-label` attribute of the element with the `toolbar` role. Use the property to tell several toolbars on one page apart. If the property is not set, the toolbar is named by the `aria_toolbar` locale key, which can be [translated](common_features/accessibility_support.md#localization-of-screen-reader-strings) like the other screen-reader strings. + +**Related articles**: +- [Accessibility support: Toolbar, Menu, Sidebar and Ribbon](common_features/accessibility_support.md#toolbar-menu-sidebar-and-ribbon) +- [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) + +@changelog: added in v9.4 diff --git a/docs/window/api/api_overview.md b/docs/window/api/api_overview.md index c1d0118f6..a0d22e333 100644 --- a/docs/window/api/api_overview.md +++ b/docs/window/api/api_overview.md @@ -44,6 +44,7 @@ description: You can explore the API of Window in the documentation of the DHTML | Name | Description | | ------------------------------------------------ | ------------------------------------------------------- | +| [](window/api/window_arialabel_config.md) | @getshort(window/api/window_arialabel_config.md) | | [](window/api/window_closable_config.md) | @getshort(window/api/window_closable_config.md) | | [](window/api/window_css_config.md) | @getshort(window/api/window_css_config.md) | | [](window/api/window_footer_config.md) | @getshort(window/api/window_footer_config.md) | diff --git a/docs/window/api/window_arialabel_config.md b/docs/window/api/window_arialabel_config.md new file mode 100644 index 000000000..0c285c703 --- /dev/null +++ b/docs/window/api/window_arialabel_config.md @@ -0,0 +1,43 @@ +--- +sidebar_label: ariaLabel +title: JavaScript Window - ariaLabel Config +description: Set the accessible name of DHTMLX Window with the ariaLabel property. Screen readers announce it when the window opens, and without it the name comes from the window title or from the aria_dialog locale key of DHTMLX Suite. +--- + +# ariaLabel + +@short: Optional. Sets the accessible name of the window, which a screen reader announces when the window opens + +#### Usage + +~~~ts +ariaLabel?: string; +~~~ + +#### Example + +~~~jsx +const dhxWindow = new dhx.Window({ + ariaLabel: "Order details", + modal: true +}); + +dhxWindow.show(); +~~~ + +@descr: +The value is rendered as the `aria-label` attribute of the element with the `dialog` role. A window is exposed as a dialog, so it needs a name of its own to be distinguishable from the rest of the page. + +The name is resolved in the following order: + +1. the `ariaLabel` value, when it is set; +2. the [`title`](window/api/window_title_config.md) value, when the window has a header title; +3. the `aria_dialog` locale key, which can be [translated](common_features/accessibility_support.md#localization-of-screen-reader-strings) like the other screen-reader strings. + +Set `ariaLabel` explicitly when the visible title is not descriptive enough on its own, or when the window has no title at all. + +**Related articles**: +- [Accessibility support: Window](common_features/accessibility_support.md#window) +- [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) + +@changelog: added in v9.4 diff --git a/sidebars.js b/sidebars.js index 22cec0b12..1edee64eb 100644 --- a/sidebars.js +++ b/sidebars.js @@ -4377,6 +4377,7 @@ module.exports = { image: '/img/docusaurus.png' }, items: [ + "toolbar/api/toolbar_arialabel_config", "toolbar/api/toolbar_css_config", "toolbar/api/toolbar_data_config", "toolbar/api/toolbar_menucss_config", @@ -4642,6 +4643,7 @@ module.exports = { image: '/img/docusaurus.png' }, items: [ + "window/api/window_arialabel_config", "window/api/window_closable_config", "window/api/window_css_config", "window/api/window_footer_config", From d5030f5faecf5640184b6bded08408be5d989f74 Mon Sep 17 00:00:00 2001 From: Masha_Rudenko Date: Wed, 7 Oct 2026 15:02:35 +0300 Subject: [PATCH 08/10] [update] 9.4 what's new: accessibility entries - Toolbar/Window entry links to the new ariaLabel pages and to title - "Screen-reader announcements" and spaced Shift + Tab, matching the accessibility guides --- docs/whatsnew.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/whatsnew.md b/docs/whatsnew.md index 4cde0b37a..e6d75176d 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -72,9 +72,9 @@ Released on October , 2026 - Colorpicker/Message/Toolbar/Window. The accessible names generated by the widgets are stored in the locale and can be [translated](common_features/accessibility_support.md#localization-of-screen-reader-strings). - Grid. Keyboard navigation: - - Tab and Shift+Tab move cell by cell through the header, the body and the footer and leave the grid at its ends, and the arrow keys move between cells whether or not a selection module is enabled. + - Tab and Shift + Tab move cell by cell through the header, the body and the footer and leave the grid at its ends, and the arrow keys move between cells whether or not a selection module is enabled. -- Grid. Screen reader announcements: +- Grid. Screen-reader announcements: - The new [`announce()`](grid/api/grid_announce_method.md) method sends a message to the live region of the grid, so a screen reader announces a status change without moving focus. - Sorting, filtering, data loading and the range corrections of a numeric editor are [announced through the same live region](grid/accessibility.md#announcing-dynamic-changes), and the announced texts are [translated through the locale](grid/localization.md#announcements). @@ -82,7 +82,7 @@ Released on October , 2026 - A bar is a single tab stop. The arrow keys, Home and End move between its controls, and menus open and close from the keyboard, returning focus to the control that opened them. - Two-state and grouped buttons report their state, and a button built from custom HTML takes its accessible name from its tooltip. -- Toolbar/Window. The `ariaLabel` configuration option sets the accessible name of a toolbar or a window. A window without `ariaLabel` is named by its `title`. +- Toolbar/Window. The `ariaLabel` configuration option of [Toolbar](toolbar/api/toolbar_arialabel_config.md) and [Window](window/api/window_arialabel_config.md) sets the accessible name of a toolbar or a window. A window without `ariaLabel` is named by its [`title`](window/api/window_title_config.md). For additional information, refer to the [Accessibility support](common_features/accessibility_support.md) and [Grid accessibility](grid/accessibility.md) guides, and to the Grid [Accessibility Conformance Report](grid/accessibility_conformance_report.md). From ab8b6325e1c0bc0f4bc48b5cf5479e2ff1d2d086 Mon Sep 17 00:00:00 2001 From: Masha_Rudenko Date: Wed, 7 Oct 2026 15:19:22 +0300 Subject: [PATCH 09/10] [update] Toolbar/Window ariaLabel as a v9.3.12 feature - ariaLabel changelog for Toolbar and Window set to v9.3.12, where the option was introduced and announced in what's new - Toolbar/Window ariaLabel bullet removed from the 9.4 accessibility updates, since the v9.3.12 release notes already cover it --- docs/toolbar/api/toolbar_arialabel_config.md | 2 +- docs/whatsnew.md | 2 -- docs/window/api/window_arialabel_config.md | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/toolbar/api/toolbar_arialabel_config.md b/docs/toolbar/api/toolbar_arialabel_config.md index a5f1dc7e2..fbff8df5f 100644 --- a/docs/toolbar/api/toolbar_arialabel_config.md +++ b/docs/toolbar/api/toolbar_arialabel_config.md @@ -30,4 +30,4 @@ The value is rendered as the `aria-label` attribute of the element with the `too - [Accessibility support: Toolbar, Menu, Sidebar and Ribbon](common_features/accessibility_support.md#toolbar-menu-sidebar-and-ribbon) - [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) -@changelog: added in v9.4 +@changelog: added in v9.3.12 diff --git a/docs/whatsnew.md b/docs/whatsnew.md index e6d75176d..bb23ba90d 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -82,8 +82,6 @@ Released on October , 2026 - A bar is a single tab stop. The arrow keys, Home and End move between its controls, and menus open and close from the keyboard, returning focus to the control that opened them. - Two-state and grouped buttons report their state, and a button built from custom HTML takes its accessible name from its tooltip. -- Toolbar/Window. The `ariaLabel` configuration option of [Toolbar](toolbar/api/toolbar_arialabel_config.md) and [Window](window/api/window_arialabel_config.md) sets the accessible name of a toolbar or a window. A window without `ariaLabel` is named by its [`title`](window/api/window_title_config.md). - For additional information, refer to the [Accessibility support](common_features/accessibility_support.md) and [Grid accessibility](grid/accessibility.md) guides, and to the Grid [Accessibility Conformance Report](grid/accessibility_conformance_report.md). ### Fixes diff --git a/docs/window/api/window_arialabel_config.md b/docs/window/api/window_arialabel_config.md index 0c285c703..5efe584b4 100644 --- a/docs/window/api/window_arialabel_config.md +++ b/docs/window/api/window_arialabel_config.md @@ -40,4 +40,4 @@ Set `ariaLabel` explicitly when the visible title is not descriptive enough on i - [Accessibility support: Window](common_features/accessibility_support.md#window) - [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) -@changelog: added in v9.4 +@changelog: added in v9.3.12 From c1f8bd481ff8d2d42b92aed334eb43b8b17e39da Mon Sep 17 00:00:00 2001 From: Masha_Rudenko Date: Wed, 7 Oct 2026 16:37:55 +0300 Subject: [PATCH 10/10] [update] Toolbar/Window ariaLabel as new in v9.4 - 9.4 what's new: ariaLabel entries for Toolbar and Window under New functionality, in the same format as the Grid ariaLabel entry; the bullet in the 9.4 accessibility updates is removed - 9.3.12 what's new: the ariaLabel item is removed; its dialog focus order note is kept as a separate Window item - ariaLabel API pages of Toolbar and Window: added in v9.4 --- docs/toolbar/api/toolbar_arialabel_config.md | 2 +- docs/whatsnew.md | 15 +++++++++++---- docs/window/api/window_arialabel_config.md | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/toolbar/api/toolbar_arialabel_config.md b/docs/toolbar/api/toolbar_arialabel_config.md index fbff8df5f..a5f1dc7e2 100644 --- a/docs/toolbar/api/toolbar_arialabel_config.md +++ b/docs/toolbar/api/toolbar_arialabel_config.md @@ -30,4 +30,4 @@ The value is rendered as the `aria-label` attribute of the element with the `too - [Accessibility support: Toolbar, Menu, Sidebar and Ribbon](common_features/accessibility_support.md#toolbar-menu-sidebar-and-ribbon) - [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) -@changelog: added in v9.3.12 +@changelog: added in v9.4 diff --git a/docs/whatsnew.md b/docs/whatsnew.md index bb23ba90d..5f75a39fa 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -60,6 +60,16 @@ Released on October , 2026 - The ability to [render HTML content in the titles of tabs](tabbar/configuring_tabbar.md#html-content-in-tab-titles), for example, an icon or a badge next to the name of a tab - new Tabbar configuration property: [`tabTemplate`](tabbar/api/tabbar_tabtemplate_config.md) +#### Toolbar + +- The ability to [give a toolbar an accessible name](common_features/accessibility_support.md#toolbar-menu-sidebar-and-ribbon), so that a screen reader can tell several toolbars on one page apart + - new Toolbar configuration property: [`ariaLabel`](toolbar/api/toolbar_arialabel_config.md) + +#### Window + +- The ability to [give a window an accessible name](common_features/accessibility_support.md#window) that differs from its [`title`](window/api/window_title_config.md), or to name a window that has no title + - new Window configuration property: [`ariaLabel`](window/api/window_arialabel_config.md) + ### Updates - DataCollection. The [`changeId()`](data_collection/api/datacollection_changeid_method.md) method returns the new id of the item @@ -166,10 +176,7 @@ Released on September 24, 2026 - Buttons built from custom HTML now expose an accessible name (**WCAG 4.1.2**, **WCAG 2.5.3**). - Two-state buttons report whether they are pressed, both in the bar and in a menu, and mutually exclusive choices are announced as a group (**WCAG 1.3.1**, **WCAG 3.2.4**). -- Toolbar/Window. The `ariaLabel` configuration option: - - It sets the accessible name announced for a toolbar or a dialog (**WCAG 2.4.6**, **WCAG 4.1.2**). - - A Window without `ariaLabel` is named by its `title`; when neither is set, each widget falls back to its own locale key, `aria_toolbar` or `aria_dialog`. - - Dialog focus order and the reported modal state now follow the ARIA dialog pattern. +- Window. Dialog focus order and the reported modal state now follow the ARIA dialog pattern. ### Fixes diff --git a/docs/window/api/window_arialabel_config.md b/docs/window/api/window_arialabel_config.md index 5efe584b4..0c285c703 100644 --- a/docs/window/api/window_arialabel_config.md +++ b/docs/window/api/window_arialabel_config.md @@ -40,4 +40,4 @@ Set `ariaLabel` explicitly when the visible title is not descriptive enough on i - [Accessibility support: Window](common_features/accessibility_support.md#window) - [Localization of screen-reader strings](common_features/accessibility_support.md#localization-of-screen-reader-strings) -@changelog: added in v9.3.12 +@changelog: added in v9.4
Enteropens the editor in the selected cell. If the editor is currently opened - closes the editor and saves changesopens the editor in the active cell or toggles a boolean cell. If the editor is currently opened - closes the editor and saves changes
F2opens the editor in the active cell (not for boolean columns)
Spacetoggles the value of a boolean cell
Escapecloses the editor of the selected cell without savingcloses the editor of the active cell without saving
Delete