Accessibility Conformance Report — fcharts 0.1.0

VPAT® 2.5 EU — EN 301 549

⚠️ DRAFT — not signed. 10 criteria require human attestation before this report is final.

Product & evaluation

FieldValue
Productfcharts
Version0.1.0
DescriptionCharts that render 100k+ points at 60fps and pass the accessibility audit at the same time.
Report date2026-06-05
StandardsEN 301 549 (the EAA harmonized standard); Chapter 9 references WCAG 2.1 Level A/AA. This report maps WCAG 2.2 Level A + AA — a superset of 2.1 — so the EN 301 549 web requirements are fully covered, with the 6 newer 2.2 criteria as additional assurance.
URL
Evaluation methodsAutomated axe-core scan (scoped to the chart); Functional probes: keyboard navigation, zoom, Escape-dismiss, live-region announcement; Computed contrast on library-controlled pairs; DOM-semantics assertions (roles, names, table, target size); Manual attestation for perceptual + real-AT + integration-context criteria
Component scopeEverything the library renders inside its container (.fc-root): the <canvas> data layer, the DOM axis ticks, the legend, the focusable data surface, the live region, the hidden data table, the readout, and the embedded JSON summary. Page-level criteria are the host application’s.

Automated/hybrid rows are re-proven on every run by this gate; manual-attestation rows are listed for human sign-off. axe-clean alone is necessary, not sufficient.

Conformance terms

Summary

33 Supports · 2 Partially Supports · 20 Not Applicable across 55 WCAG 2.2 A/AA success criteria.

LevelSupportsPartially SupportsDoes Not SupportNot Applicable
Level A190012
Level AA14208

Perceivable

CriteriaLevelConformance LevelRemarks and Explanations
1.1.1 Non-text ContentASupportsCanvas is aria-hidden; a text alternative is always present (hidden data table, natural-language summary, and embedded JSON). Whether the integrator-supplied ariaLabel/axis labels meaningfully describe the chart is an authoring judgment. (verified: automated)
1.2.1 Audio-only and Video-only (Prerecorded)ANot ApplicableThe component renders no audio/video; it draws vector marks plus a DOM overlay. (verified: automated)
1.2.2 Captions (Prerecorded)ANot ApplicableNo synchronized media. (verified: automated)
1.2.3 Audio Description or Media Alternative (Prerecorded)ANot ApplicableNo video content. (verified: automated)
1.2.4 Captions (Live)AANot ApplicableNo live media; real-time data uses a text live region. (verified: automated)
1.2.5 Audio Description (Prerecorded)AANot ApplicableNo video content. (verified: automated)
1.3.1 Info and RelationshipsASupportsTable/legend/surface relationships are programmatic (scoped table headers, aria-pressed legend, role="application" surface). R1 closed the last gap: the data-table x-column header now uses the configured xLabel (falls back to "x"). (verified: automated)
1.3.2 Meaningful SequenceASupportsDOM is appended in reading order (legend before plot; table reads x-then-series in ascending sample order); .fc-root is flex-column with no order/*-reverse/float. (verified: automated)
1.3.3 Sensory CharacteristicsASupportsInstructions name keys, not shape/position; series are identified by text name and values by axis name, not color or location. Avoiding sensory-only language in author labels remains an attestation. (verified: hybrid)
1.3.4 OrientationAASupportsNo orientation lock; .fc-root is a fluid 100% flex-column re-measured by ResizeObserver. No @media (orientation), transform:rotate, or screen.orientation lock. (verified: automated)
1.3.5 Identify Input PurposeAANot ApplicableThe chart component has no form inputs that collect information about the user. (verified: automated)
1.4.1 Use of ColorASupportsColour is never the sole differentiator. R5 auto-assigns a distinct dash pattern per series (canvas setLineDash) when the integrator gives none, and the legend swatch mirrors it (a dashed <line>, or a <rect> for area) — so two series are told apart by dash/shape, not only hue. Legend, table, and readout already give colour-free identity. The legend-semantics check asserts the swatches are mutually distinct beyond colour. (verified: automated)
1.4.2 Audio ControlANot ApplicableThe component plays no audio. (verified: automated)
1.4.3 Contrast (Minimum)AAPartially SupportsLibrary DOM text passes AA on a light background (ticks 7.56:1, body 10.31:1, readout 16.98:1) and the legend hidden state no longer dims text (R8). The one residual gap is inherently integrator-dependent: the effective host background behind the tick/legend text is the host page’s, so the on-host ratio is theirs to confirm (the readout sets its own background, so it is host-independent). (verified: hybrid)
1.4.4 Resize TextAASupportsR7 expresses every label font (tick, axis title, legend, legend state, readout) in rem, so text-only zoom — raising the root font size without page zoom — enlarges them, and the fluid container re-measures so the chart follows. The resize-text-rem check proves the tick font scales with the root font size. (verified: automated)
1.4.5 Images of TextAASupportsAll readable text is real DOM text (axis ticks/titles as spans, legend as buttons, table as markup). A repo-wide search confirms the canvas renders zero fillText/strokeText/measureText/ctx.font. (verified: automated)
1.4.10 ReflowAASupportsFluid canvas with no library min-width, a reflowing wrap legend, and the 2-D chart-geometry exception. R7 closed the tick-overlap gap: effectiveTickCount thins tick density as the plot narrows (≥1 label per 64/28 px) so labels do not collide at ~320 CSS px or high zoom. The reflow-adaptive check proves the x-tick count drops when narrowed. Whether the integrator’s surrounding container scrolls in 2-D remains theirs. (verified: hybrid)
1.4.11 Non-text ContrastAASupportsThe essential graphical objects — the data marks — clear 3:1: R6 added an 8-colour default palette each verified ≥3:1 on BOTH a light and a dark chart background (palette.test.ts), assigned by index when the integrator gives no colour, and the contrast-marks check re-proves each legend swatch ≥3:1 vs the documented background. Focus ring #2563eb = 5.17:1. Gridlines are decorative reference lines (not the 1.4.11 object). If the integrator overrides the palette with a low-contrast colour, that becomes their attestation. (verified: hybrid)
1.4.12 Text SpacingAASupportsNo library text sets line-height/letter-spacing/word-spacing that would clip under a user-spacing override, and there is no overflow:hidden or fixed height on visible text (the only letter-spacing is .12em on the short uppercase axis title). (verified: automated)
1.4.13 Content on Hover or FocusAASupportsThe readout is Hoverable (pointer-events:none) and Persistent (hidden only on pointer-leave/blur, never on a timer). R3 made it Dismissible: Escape clears readout + crosshair via dismissCursor() without moving focus. (verified: automated)

Operable

CriteriaLevelConformance LevelRemarks and Explanations
2.1.1 KeyboardASupportsCursor navigation, legend toggle, and zoom are all keyboard-operable. R2 added keyboard zoom (+/=/-/_) mirroring wheel-zoom centered on the cursor, so every pointer function now has a keyboard path. (verified: automated)
2.1.2 No Keyboard TrapASupportsTab/Shift+Tab are never intercepted; onKeyDown only preventDefaults the six navigation keys. No focus() trap, aria-modal, or inert; drag pointer-capture is released on up/cancel. (verified: automated)
2.1.4 Character Key ShortcutsASupportsThe handled key set is only ArrowRight/Left/Up/Down/Home/End plus Shift — no single letter/number/punctuation shortcut and no accesskey, so there is nothing to remap or turn off. (verified: automated)
2.2.1 Timing AdjustableASupportsNo time limits, sessions, or countdowns. The only timers are a 150ms table-update throttle and a 100ms announce debounce — output coalescers that delay no user action. Integrators who stream data on a timer own their own pause/stop controls. (verified: hybrid)
2.2.2 Pause, Stop, HideASupportsRendering is strictly on-demand (one frame per request, no rAF recursion); no auto-motion, auto-scroll, blink, or auto-update by default. The only animation is a 0.08s readout fade, far under 5s and disabled under prefers-reduced-motion. (verified: hybrid)
2.3.1 Three Flashes or Below ThresholdASupportsNothing flashes: render() does a single clearRect+repaint per user-driven frame and the crosshair is a static dashed line. No @keyframes/blink/strobe; cadence is bounded by user input. A luminance-over-time probe over arbitrary author data is a human check. (verified: hybrid)
2.4.1 Bypass BlocksANot ApplicablePage-level; the chart is a single focus stop, not repeated page blocks. (verified: automated)
2.4.2 Page TitledANot ApplicablePage-level; the host owns the document title. (verified: automated)
2.4.3 Focus OrderASupportsFocus order is legend buttons then the data surface, matching DOM/reading order; tabIndex 0 is the only focus-affecting statement (no positive tabindex, reorder, or autofocus). (verified: automated)
2.4.4 Link Purpose (In Context)ANot ApplicableThe component renders no links. (verified: automated)
2.4.5 Multiple WaysAANot ApplicablePage/site-level navigation concern. (verified: automated)
2.4.6 Headings and LabelsAASupportsThe component emits no section headings (host owns those) and its labels are descriptive: surface aria-label, legend group + buttons, table caption + scoped headers. R1 made the table x-column header use the configured xLabel rather than the hardcoded "x". (verified: automated)
2.4.7 Focus VisibleAASupportsThe data surface shows a :focus-visible ring, upgraded to a real outline under prefers-contrast:more and a system-color Highlight outline under forced-colors:active; legend buttons keep the native UA focus ring (no outline:none). (verified: automated)
2.4.11 Focus Not Obscured (Minimum)AAPartially SupportsThe component never fully obscures its own focused surface (the readout is a small tooltip over a fraction of the large surface). Residual gap is integrator-dependent: host-page sticky headers, toolbars, or overlays could obscure the focused chart. (verified: hybrid)
2.5.1 Pointer GesturesASupportsAll pointer interactions are single-pointer and not path-based: drag-pan depends on the net horizontal delta (not the trajectory), zoom is wheel-driven, and no multipoint gesture exists. "Not path-based / not multipoint" is a human judgment. (verified: manual-attestation)
2.5.2 Pointer CancellationASupportsNo function executes on the down-event: onPointerDown only sets drag state and captures the pointer; the pan is reversible before release (computed against the immutable start domain) and finalizes on pointerup. Legend buttons activate on the up-event. (verified: automated)
2.5.3 Label in NameASupportsThe legend buttons are the only controls with visible text labels, and each button's accessible name is computed from the same visible series-name text node it displays (swatch is aria-hidden) — verifiable by axe label-in-name. (verified: automated)
2.5.4 Motion ActuationANot ApplicableNo device-motion-actuated functionality. (verified: automated)
2.5.7 Dragging MovementsAASupportsR4 added pan pagers: two real ‹/› buttons that step the visible window earlier/later with a single-pointer click — the non-dragging alternative to drag-to-pan that 2.5.7 requires. They appear when the view is zoomed in (panning has an effect) and, being buttons, are keyboard- and AT-operable too. The single-pointer-pan check zooms in, then confirms the pagers are present and a click shifts the domain. (verified: automated)
2.5.8 Target Size (Minimum)AASupportsThe data surface fills the plot inset and far exceeds 24x24. R9 gave legend buttons min-height:24px;min-width:24px;line-height:1.1, guaranteeing 24x24 regardless of host fonts — assertable by a computed-box check. (verified: automated)

Understandable

CriteriaLevelConformance LevelRemarks and Explanations
3.1.1 Language of PageANot ApplicablePage-level; the host sets the document language. (verified: automated)
3.1.2 Language of PartsAASupportsR10 made every fixed UI string (keyboard help, legend label, per-series state words, table caption, data summary) overridable via the strings option, so an integrator on a non-English page can match the document language; defaults remain English. (verified: automated)
3.2.1 On FocusASupportsFocusing the surface only sets cursorActive, announces the current point via the polite live region, and re-renders the crosshair in place — no focus move, navigation, window, or form submission. (verified: automated)
3.2.2 On InputASupportsNavigation keys move the cursor/pan in place and legend buttons toggle visibility in place via toggleSeries; no path changes context (no navigation, submission, or focus move). (verified: automated)
3.2.3 Consistent NavigationAANot ApplicableCross-page navigation concern; not a single component. (verified: automated)
3.2.4 Consistent IdentificationAASupportsAll legend buttons are produced by one build() routine with identical structure (swatch + name + state) and uniform aria-pressed; the surface carries a stable role="application" + aria-roledescription on every instance. (verified: automated)
3.2.6 Consistent HelpANot ApplicablePage/site-level help mechanism concern. (verified: automated)
3.3.1 Error IdentificationANot ApplicableNo form inputs / errors. (verified: automated)
3.3.2 Labels or InstructionsASupportsThough the component has no data-entry fields, its interactive controls carry instructions: the surface aria-label embeds the full keyboard model plus a pointer to the data table, and the legend group + buttons are labeled with shown/hidden state. (verified: automated)
3.3.3 Error SuggestionAANot ApplicableNo form inputs / errors. (verified: automated)
3.3.4 Error Prevention (Legal, Financial, Data)AANot ApplicableNo transactions / legal commitments. (verified: automated)
3.3.7 Redundant EntryANot ApplicableNo multi-step entry of information. (verified: automated)
3.3.8 Accessible Authentication (Minimum)AANot ApplicableNo authentication. (verified: automated)

Robust

CriteriaLevelConformance LevelRemarks and Explanations
4.1.2 Name, Role, ValueASupportsName and Role are exposed for every control. R11 closed the Value half: the focused sample is a queryable aria-describedby target (fc-active-{n}) updated in lockstep with each cursor move, and the legend state span is aria-hidden so each name stays the stable series name. (verified: automated)
4.1.3 Status MessagesAASupportsCursor moves not conveyed through focus are announced via a dedicated aria-live="polite" aria-atomic="true" region appended at construction, debounced 100ms. Presence + DOM-before-update ordering are automatable; that announcements are actually spoken from inside role="application" is attested. (verified: hybrid)

Additional adaptations (beyond Level A/AA)

FeatureConformance LevelRemarks and Explanations
prefers-reduced-motion supportSupportsThe only shipped CSS transition (0.08s readout fade) is removed under prefers-reduced-motion:reduce, and the reducedMotion option is auto-detected via matchMedia and threaded into every RenderScene. There is essentially no decorative/looping motion to suppress. (verified: automated)
Windows High Contrast Mode (forced-colors)SupportsR12 made the canvas participate in forced colors. The renderer probes the system palette (CanvasText/Canvas/GrayText/Highlight) when forced-colors:active and repaints the grid, axes, series, and crosshair in those colors instead of author colors; a media listener tracks live toggles. The DOM overlay already adapts and the focus ring uses Highlight. The forced-colors-canvas check confirms the bitmap actually changes when forced colors turn on. (verified: automated)

Chapter 4: Functional Performance Statements (EN 301 549)

Derived from the WCAG criteria that serve each statement — never stated independently.

ClauseCriteriaConformance LevelRemarks and Explanations
4.2.1Usage without visionSupportsKeyboard-operable with screen-reader announcements and a full data-table alternative. (derived from 1.1.1, 1.3.1, 2.1.1, 4.1.2, 4.1.3)
4.2.2Usage with limited visionPartially SupportsResizes and reflows; some contrast/zoom aspects depend on the host background and theme. (derived from 1.4.3, 1.4.4, 1.4.10, 1.4.11)
4.2.3Usage without perception of colorSupportsSeries carry a distinct dash pattern (mirrored in the legend swatch) in addition to color, and identity is also available via the legend, data table, and readout. (derived from 1.4.1)
4.2.4Usage without hearingSupportsNo audio content is produced.
4.2.5Usage with limited hearingSupportsNo audio content is produced.
4.2.6Usage without vocal capabilitySupportsNo speech input is required.
4.2.7Usage with limited manipulation or strengthSupportsFull keyboard operation (including zoom), and pan pagers give a single-pointer, non-dragging alternative to drag-pan (R4) — so pointer-only and keyboard-only users are both covered. Targets meet 24×24px. (derived from 2.1.1, 2.5.1, 2.5.7, 2.5.8)
4.2.8Usage with limited reach and strengthSupportsInteractive targets meet the 24×24px minimum. (derived from 2.5.8)
4.2.9Minimize photosensitive seizure triggersSupportsNothing flashes; rendering is user-driven, not strobing. (derived from 2.3.1)
4.2.10Usage with limited cognition, language, or learningSupportsPredictable behavior, labeled controls with instructions, and no time limits. (derived from 3.2.1, 3.2.2, 3.3.2, 2.2.1)
4.2.11PrivacySupportsThe component handles no personal data.

Chapter 11: Software

fcharts is a UI component embedded in a host page; most software clauses are the platform/host responsibility and are marked Not Applicable.

ClauseCriteriaConformance LevelRemarks and Explanations
11.5.2Name, role, state, value of user-interface componentsSupportsThe component exposes name, role, and value for its controls (derived from 4.1.2).
11.6 / 11.7Platform accessibility services / closed functionalityNot ApplicableThe chart runs in the host web browser; platform accessibility services and assistive-technology interoperability are provided by the user agent and OS, not by this component.

Chapter 12: Documentation & Support

ClauseCriteriaConformance LevelRemarks and Explanations
12.1.1Accessibility and compatibility features documentedSupportsThis ACR, the README accessibility section, and llms.txt document the chart’s accessibility features, keyboard model, and the localizable strings option.
12.2.4Documentation available in an accessible electronic formatSupportsDocumentation is plain Markdown/HTML, itself accessible and machine-readable.

Attestation

The following criteria rest on human attestation and must be confirmed and signed before this report is final:

Legal

This Accessibility Conformance Report is provided for informational purposes and reflects an evaluation of the named version. "VPAT" is a registered service mark of the Information Technology Industry Council (ITI). Conformance is scoped to the chart component only (see Component Scope); the embedding application and page remain responsible for page-level criteria. This document is not legal advice.

Generated 2026-06-05.