WCAG 2.1 · PlotviewLevel A & AA criteria
50 criteria in total — 30 at Level A and 20 at Level AA. Every criterion is listed, including those not applicable to plotview, with the justification stated inline.
Status legend: Supports Partial Does Not Support Not Applicable
| SC # | Criterion | Level | Status | Notes / Evidence |
|---|---|---|---|---|
| 1.1.1 | Non-text Content | A | Partially Supports | Interactive controls carry accessible names (292 aria-label occurrences across 74 source files). Decorative icons are marked aria-hidden (sweeps #3085/#3174/#3208, May 2026). Box-plot components expose role=button with value-range labels (boxPlotElement.js:335,442). The plot exposes hidden textual summaries (#tde-tools-summary, #tde-plot-summary; plotView.js:2458-2627) referenced via aria-describedby from the main wrapper and plot background, and the linked data table gives a full text equivalent of the plotted data. Sonification (beta) offers an auditory alternative. Gap: plotted case icons receive an aria-label only while focused (dragHandler.js:244,251), so at rest they have no text alternative (axe aria-command-name, 14 nodes per plot). Target: Q4 2026. |
| 1.2.1 | Audio-only and Video-only (Prerecorded) | A | Not Applicable | The product contains no prerecorded audio-only or video-only content. |
| 1.2.2 | Captions (Prerecorded) | A | Not Applicable | No prerecorded synchronized media is embedded in the product. |
| 1.2.3 | Audio Description or Media Alternative (Prerecorded) | A | Not Applicable | No prerecorded video content. |
| 1.2.4 | Captions (Live) | AA | Not Applicable | No live media. |
| 1.2.5 | Audio Description (Prerecorded) | AA | Not Applicable | No prerecorded video content. |
| 1.3.1 | Info and Relationships | A | Supports | Structure is conveyed programmatically: the tool wrapper is the one role=main landmark, and the toolbar, case card, table view and plot area are labelled role=region landmarks — "Toolbar", "Case Card Panel", "Table View" / "Summary View" / "Sample Statistics", "Plot Area" (#3430, September 2026). The plot background is a named graphic (role=graphics-symbol) rather than a second main. Plot titles are role=heading with aria-level (topMarginView.js:145-147), and widget roles (slider, button, menu, menuitemradio, option, status) with aria-expanded / aria-pressed states are used throughout. Grid navigation announces header and cell value together (gridAccessibility.js). Menu items now carry the state their role requires: menuitemradio and menuitemcheckbox items always emit aria-checked, the language list and the order-by-attribute list are radio groups, and aria-selected (prohibited on menuitem and link) is gone (#3429, September 2026). |
| 1.3.2 | Meaningful Sequence | A | Supports | DOM order follows visual order; plot controls precede plotted cases in tab order (#2209). Grid navigation announces 'Header, Value' in reading order. |
| 1.3.3 | Sensory Characteristics | A | Supports | Instructions do not rely on shape, size, location, or sound alone. Keyboard shortcuts use named modifier keys and have a written, searchable reference (KeyboardShortcutsPopup.jsx). |
| 1.3.4 | Orientation | AA | Supports | Layout reflows in portrait and landscape; no orientation lock. |
| 1.3.5 | Identify Input Purpose | AA | Not Applicable | The product collects none of the personal-information input types enumerated in WCAG 1.3.5 (name, email, phone, address, etc.). Inputs are dataset attribute names, formulas, and numeric values. |
| 1.4.1 | Use of Color | A | Supports | Three data-color schemes (Standard, Colorblind Safe, Grayscale) are selectable in the Accessibility panel. Legends, axis labels, and text accompany every color encoding. |
| 1.4.2 | Audio Control | A | Supports | Sonification audio is user-initiated only and provides Play/Pause, Stop, Restart, speed and volume controls; speed and volume changes are announced via aria-live (#3079). |
| 1.4.3 | Contrast (Minimum) | AA | Supports | Default theme body text is #3d4146 on #ffffff (10.28:1) and the Reverse Contrast theme (white on black) is available; the theme-color focus ring (#2563EB) is 5.17:1 on white. Secondary text was raised to AA in September 2026 (#3432): default subtext #646870 (5.59:1), input placeholder #6b7076 (4.99:1), and reverse-contrast subtext and cell subtext #8f8a82 (6.13:1 on black). Every text token in both themes now measures at least 4.5:1 against its background (src/scss/base/_themes.scss). The OS prefers-contrast: high media query currently affects only the box-plot focus outline. axe-core reports no contrast violations; SVG plot text (5-12 nodes) cannot be evaluated automatically and was checked by inspection. |
| 1.4.4 | Resize Text | AA | Supports | Plot text is scalable from the Accessibility panel across an 8 to 32 px ramp (0.57x to 2.29x of the 14 px base; constants.js:58-108) and case (data point) size from 3 to 80 px; preferences persist. The HTML toolbar and panels scale with browser zoom, which the layout tolerates to 200%. |
| 1.4.5 | Images of Text | AA | Supports | All labels, axis ticks, legends, and captions are live text (SVG text or HTML), not raster images. |
| 1.4.10 | Reflow | AA | Supports | Verified at 320 CSS px — the layout viewport of a 1280 px window at 400% zoom — with measurements read from the live DOM in headless Chrome (#3437, October 2026). The page chrome never scrolls horizontally (documentElement.scrollWidth 320 = clientWidth at both 320×640 and 320×200); the left panel narrows to 200 px, the plot toolbar collapses from 20 items to 2 plus a More Options kebab, and every item in that menu stays reachable by wheel and by arrow key. The data grid scrolls horizontally inside its own overflow-x: auto container, which 1.4.10 exempts as content requiring two-dimensional layout. One defect was found and fixed: the 40 px table-header bar used overflow: hidden and clipped 167 px of controls — the Summary tab, the data-type tips toggle and the expand button — out of a pointer user's reach below roughly 490 px. It now scrolls horizontally (src/scss/modules/_table-tab.scss), with a thumb that meets the 3:1 non-text minimum in every theme; at normal widths nothing overflows and no scrollbar appears. |
| 1.4.11 | Non-text Contrast | AA | Supports | Focus indicators meet 3:1: the theme outline color #2563EB (5.17:1 on white, 4.06:1 on black) forms the global 1 px inset ring and the 2 px toolbar and menu rings; the SVG handle outline #4A90E2 is 3.29:1 on white. Control boundaries were raised to AA in September 2026 (#3432): default-theme borders, dividers, checkbox, button and input borders are #767b81 (4.27:1 on white) and their reverse-contrast counterparts #6e6a63 (3.90:1 on black), so control edges no longer rely on background fill alone (src/scss/base/_themes.scss). The hairlines still below 3:1 are decorative — the drag-target cell outline and the select arrow separator — and do not identify a control or a state. |
| 1.4.12 | Text Spacing | AA | Supports | Tested with all four 1.4.12 overrides active as a user stylesheet from document load — line-height 1.5, paragraph spacing 2em, letter-spacing 0.12em, word-spacing 0.16em — at 1280×800 and at 320 CSS px (#3437, October 2026). Nothing is lost or cut off: the toolbar keeps all 20 items on one row and its spacer self-selects a wider class to absorb the longer labels, menu items and card labels wrap instead of clipping (0 of 19 More Options items overflow at 320 px), and the page gains no horizontal scrolling. A whole-page clipping sweep returned only fixed-height bars that lost 4–9 px of vertical slack with no glyph cut, plus the table-header overflow addressed under 1.4.10. |
| 1.4.13 | Content on Hover or Focus | AA | Supports | Tooltips remain visible while hovered (ToolTip.js:82-83) and persist until the pointer leaves or focus moves. Escape dismisses a tooltip whether it was opened by hover or by focus, and it stays dismissed for the rest of that hover (#2746, October 2026): a document-level capture keydown listener calls TooltipManager.dismiss(), which records the dismissed trigger so a follow-mode tooltip cannot reopen on the next pointer move (TooltipManager.js:33-35,54,72-74,125-134). The record clears when the pointer leaves the trigger, so hovering back in shows the tip again. Case-icon tooltips dismiss through the same path (dragHandler.js:236). |
| 2.1.1 | Keyboard | A | Partially Supports | Nearly all functionality is keyboard-operable: toolbar, menus, Accessibility panel, data table, 42 platform-aware shortcut groups (keymap.js), plotted cases (tabindex=0 when visible, plotView.js:2822; Enter toggles selection, dragHandler.js:230-238), annotations, reference lines, function curves, averages, axis handles, and the legend divider (arrow keys); the Leaflet map keeps keyboard navigation on; CodeMirror does not trap Tab. Categories on an axis or in the legend reorder with Shift+Arrow, publishing the same move as drag (#2728, September 2026). Gaps: moving cases in Mix-Up plots is drag-only (dragHandler.js:274-289); the keyboard attribute-placement menu is hidden until the attribute-context-menu preference is enabled (see 2.5.7); IncrDecrInput's text field is read-only and out of tab order, so values change only through its labeled +/- buttons (IncrDecrInput.jsx:62-66). 22 handlers still use the deprecated onKeyPress event (functionally equivalent). Target: Q4 2026. |
| 2.1.2 | No Keyboard Trap | A | Supports | No keyboard traps. Context menus and the color picker intentionally cycle Tab within themselves (utils/focusTrap.js:33-49) and always close on Escape (ContextMenuWrapper.jsx:45-48, ColorPickerUtil.js:46-71); the CodeMirror formula editor passes Tab through (Tab: false, DerivedAttributeComponent.jsx:223). The Accessibility panel is a non-modal sliding panel that Tab can leave freely. |
| 2.1.4 | Character Key Shortcuts | A | Supports | All 164 shortcut bindings in keymap.js require a modifier key (Alt/Option, Ctrl, Cmd, Shift); the only unmodified single-key handler (u, in annotation editing) is active only while that annotation has focus, which 2.1.4 exempts. |
| 2.2.1 | Timing Adjustable | A | Supports | The product imposes no session or task time limits. The marquee-selection pop-up menu's 2.5-second auto-dismiss timer was removed in October 2026 (#3435, marquee-select-menu/MarqueeSelectMenuComponent.jsx): the menu now closes only on Escape, on its new close button, on a click outside, when focus genuinely leaves it, or when an action is chosen — never on a timer, and never on pointer position. The same change traps Tab inside the menu while it is open (utils/focusTrap.js) and gives it a visible “Filter” title tied to role="menu" by aria-labelledby. Its two actions also remain available through the Ctrl/Cmd+E and Ctrl/Cmd+K shortcuts. |
| 2.2.2 | Pause, Stop, Hide | A | Supports | The only moving content is chart-transition animation, which users can disable via the Animate Graphs toggle (Accessibility panel and toolbar menu); animation defaults off when the OS prefers-reduced-motion setting is on (#3078, dynPreferences.js:39-44) and is suppressed automatically for large datasets. No auto-updating or auto-advancing content. |
| 2.3.1 | Three Flashes or Below Threshold | A | Supports | No flashing content. Transitions are smooth fades/slides and can be disabled entirely. |
| 2.4.1 | Bypass Blocks | A | Supports | A visible skip-to-main-content link is the first focusable element inside the tool and moves focus to the plot area, in Play mode as well (#3080, September 2026). ARIA landmarks let screen-reader users jump between blocks: one role=main wrapper plus labelled role=region landmarks for the toolbar, case card, table view and plot area (#3430); each region accepts programmatic focus, so a landmark jump is followed by Tab continuing inside that region rather than back at the previous position. Focus-jump shortcuts move keyboard focus directly to the toolbar (Shift+M), case card (Shift+C), plot (Shift+P), title (Shift+T), X/Y axes and legend (Shift+X/Y/L), table (Alt/Option+T), summary (Alt/Option+S), and other panels (keymap.js:197-264). |
| 2.4.2 | Page Titled | A | Supports | The product is embedded in a host page that supplies the document title; the product does not alter or suppress it. |
| 2.4.3 | Focus Order | A | Supports | Tab order follows the visual layout: toolbar, case card, plot controls, then plotted cases (#2209). The outer wrapper’s tabindex was corrected from 1 to 0 in September 2026 (#3430), so the tool no longer takes precedence over host-page controls in the page tab sequence. Context menus move focus to their first option on open and cycle Tab inside the menu (ContextMenuWrapper.jsx:27-36, utils/focusTrap.js), and return focus to the control that opened them when they close by Escape, outside click or item activation (#3431, September 2026); when the closing action removes that control, focus lands on the plot region that is on screen rather than on the page body. Toolbar submenus return focus to their trigger on Escape (SubMenu.jsx:302-303), and Escape in the plot-title editor closes it and returns focus to the title, or to its edit button when the title is empty. |
| 2.4.4 | Link Purpose (In Context) | A | Supports | Interactive controls have descriptive accessible names; no generic link text. |
| 2.4.5 | Multiple Ways | AA | Supports | Features are reachable via toolbar, keyboard shortcut, and in-app menus. The Accessibility panel opens from the toolbar or Alt+Z (Option+Z on macOS); the Keyboard Shortcuts reference from the toolbar or Ctrl+/ (Cmd+/ on macOS). |
| 2.4.6 | Headings and Labels | AA | Supports | Plot titles are exposed as headings (role=heading, aria-level=2); panel headings and control labels describe purpose. |
| 2.4.7 | Focus Visible | AA | Supports | Every focusable element shows a focus indicator: the global focus-visible style is a two-layer inset ring (1 px theme outline color plus 1 px white, base/_config.scss:130-131); toolbar controls use a 2 px inset ring (_toolbar.scss:39-41), menu items a 2 px outline (_menu.scss:199-203), and SVG box-plot handles a 3 px solid #4A90E2 outline with 2 px offset, upgraded to 4 px currentColor under prefers-contrast: high (_a11y.scss:19-29). Inset rings are used where overflow:hidden would clip an outline. Category buttons drawn in the plot use outline rather than box-shadow, which browsers do not paint on elements inside an SVG, and the Summary View grid focus marker is visible again (#2728, September 2026). |
| 2.5.1 | Pointer Gestures | A | Supports | All functionality uses single-pointer actions; no path-based or multipoint gestures are required. |
| 2.5.2 | Pointer Cancellation | A | Supports | Interactions are single-pointer click or drag handled through Raphael drag start/move/end handlers (dragHandler.js:218-221); a drag released off-target performs no action. Down-event-only activation was not observed in code review but was not exhaustively verified for case-icon selection. |
| 2.5.3 | Label in Name | A | Supports | Accessible names contain the visible label text; toggles embed the caption plus state. |
| 2.5.4 | Motion Actuation | A | Not Applicable | No device-motion or user-motion triggered functionality. |
| 3.1.1 | Language of Page | A | Supports | All UI strings are localized through the built-in i18n layer (English plus 10 translation catalogs in src/locale; TuvaDataTools.jsx:47-58). The tool's root container carries a lang attribute and updates it when the user switches language (#3436, October 2026). The tag is read from the language list rather than the catalog key, because two keys — cantonese and mandarin — name catalogs rather than language tags and resolve to zh-cn and zh-tw (languageTagFor, utils/constants.js). Overlays and tooltips mount their wrapper on document.body, outside that root, so they stamp the same tag themselves — context menus, the marquee-selection menu, the color picker and the derived-attribute keypad through OverlayManager.js:54, tooltips through TooltipManager.js:73 (utils/uiLanguage.js). Tuva host templates declare html lang on the page itself (September 2026). |
| 3.1.2 | Language of Parts | AA | Supports | The one place the product mixes languages, the Language menu (each language named in its own script), marks every item with a lang attribute (ToolbarComponent.jsx:1906). All other UI renders in the single selected language. |
| 3.2.1 | On Focus | A | Supports | Receiving focus never changes context. |
| 3.2.2 | On Input | A | Supports | Changing a setting applies the preference in place without navigation or submission; behavior is predictable. |
| 3.2.3 | Consistent Navigation | AA | Supports | Toolbar structure and menu order are consistent across plot, table, and other views. |
| 3.2.4 | Consistent Identification | AA | Supports | Controls with the same function share the same icon and label across the interface. |
| 3.3.1 | Error Identification | A | Partially Supports | The derived-attribute formula editor reports errors in text in a role=alert live region associated with the editor via aria-describedby (DerivedAttributeComponent.jsx:264-267,611). Gaps: the Random Sample size error is a plain div with no role or association (RandomSampleComponent.jsx:615-619); attribute-name and other inputs signal invalid values with a red border only (NameDescEditComponent.jsx:160-178); the errorField id is static. Target: Q4 2026. |
| 3.3.2 | Labels or Instructions | A | Supports | All inputs in the Accessibility panel, attribute editor, and filter panels have visible labels or aria-labels, with help text where needed. |
| 3.3.3 | Error Suggestion | AA | Partially Supports | The Random Sample size error states the allowed range (RandomSampleComponent.jsx:617). Gaps: formula-editor errors are generic ('Syntax error: Please enter a valid expression', NameDescEditComponent.jsx:56-65) and do not suggest a correction; attribute-name autocomplete while typing is the only assistance. Target: Q4 2026. |
| 3.3.4 | Error Prevention (Legal, Financial, Data) | AA | Not Applicable | The product performs no legal or financial transactions; all data-analysis actions are reversible via undo/redo. |
| 4.1.1 | Parsing | A | Supports | Markup is generated by React; no duplicate ids or malformed nesting were found by axe-core. This criterion is obsolete in WCAG 2.2. |
| 4.1.2 | Name, Role, Value | A | Partially Supports | Custom widgets broadly expose role, name, and state: roles button, menu, menuitem, menuitemradio, menuitemcheckbox, option, slider, status, region, dialog; states aria-expanded, aria-pressed, aria-checked, aria-disabled, aria-haspopup. Sonification controls carry aria-labels and the Audio Trace toggle exposes aria-pressed (#3079). Category controls on the categorical axis, the color legend and Summary View are toggle buttons with a name carrying the attribute, aria-pressed and Enter/Space activation (#2728, September 2026). Menu state is complete: inactive menuitemradio items emit aria-checked, and aria-selected no longer appears where it is prohibited; axis range handles expose aria-valuemin / aria-valuemax / aria-valuetext alongside aria-valuenow, formatted by the axis scale so a frequency or percent axis reads in its own units (#3429, September 2026). Gap: plotted case icons are role=link (a selection toggle, so role=button is the correct role), receive an aria-label only while focused (dragHandler.js:244,251) so unfocused icons have no name, and carry aria-pressed, which is not permitted on links (plotView.js:2818,2975,3166). Target: Q4 2026. |
| 4.1.3 | Status Messages | AA | Partially Supports | The data table announces cell navigation through a visually hidden aria-live=assertive role=status region (gridAccessibility.js:6-12), and sonification speed and volume changes are announced via role=status live regions (SonificationSettingsComponent.jsx:128-159). Category selection on the axis, legend and Summary View is announced through a shared polite live region (categoryA11y.js, #2728, September 2026). Gaps: the plot summary elements (#tde-tools-summary, #tde-plot-summary) are hidden divs exposed only through aria-describedby, not live regions (TuvaDataTools.jsx:1552,1629), so plot-type changes, filter results, and selection counts are not announced; the box-plot live region is Safari-only (boxPlotElement.js:338-346). Target: Q4 2026. |