Accessibility
Visualizations that communicate beyond the visual.
Plotcn treats accessibility as part of the chart contract: meaningful labels, structured summaries, keyboard interaction, non-color encoding, reduced motion, and accessible data fallbacks.
Multi-Modal Accessibility Model
One shared dataset rendered into multiple sensory and input paths. Every user consumes the same core insight.
Consumes structured semantics via NVDA, VoiceOver, or JAWS. Relies on titles, descriptions, text summaries, and tables.
Accessible Name
Heading connected via aria-labelledby provides concise context.
Deterministic Summary
Natural-language text surfaces trend, peak, minimum, and overall delta.
Data Table Fallback
Semantic <table> alternative enables cell-by-cell row navigation.
Accessibility model
A data visualization is not accessible merely because its root <svg> element has an aria-label attribute. Graphical paths, bezier curves, and canvas pixels are fundamentally invisible to screen readers, while hover-triggered overlays exclude keyboard and mobile touch users.
Plotcn treats accessibility as an architectural contract composed of layered representations:
- Accessible Name: A concise, visible title communicating what the chart represents.
- Accessible Description: Contextual metadata specifying units of measure, time horizons, and coordinate dimensions.
- Structured Text Summary: Deterministic, natural-language sentences surfacing the key insight, trend direction, period peak, and minimum.
- Accessible Interaction: Full keyboard traversal, visible focus rings, roving tab indices, and touch inspection gestures.
- Structured Data Alternative: A semantic data table sharing the identical underlying data source.
Give every chart a meaningful name
Every chart must have a concise, human-readable name that answers "what am I looking at?" in less than five seconds.
Meaningful vs. generic names
- Good:
Monthly revenue, January through June 2026 - Good:
Server latency distribution by region (p99) - Bad:
Chart - Bad:
Graph 1 - Bad:
Visualization
Native heading relationships
Prefer visible headings connected via aria-labelledby over hidden label attributes. Visible labels ground the visualization for all users, including sighted users navigating with screen magnifiers:
<section aria-labelledby="revenue-chart-title" aria-describedby="revenue-chart-desc" className="rounded-xl border border-border p-6"> <header className="space-y-1 mb-4"> <h3 id="revenue-chart-title" className="text-base font-semibold text-foreground"> Monthly Revenue (H1 2026) </h3> <p id="revenue-chart-desc" className="text-xs text-muted-foreground"> Total gross revenue across all sales channels, measured in thousands of USD. </p> </header> {/* Visualization Container */} <div role="img" aria-labelledby="revenue-chart-title"> <PlotLineChart data={monthlyRevenue} /> </div></section>Add a useful description
While the accessible name provides a title, the accessible description provides operational context. A good description answers:
- What primary metrics are tracked
- What units of measurement apply (e.g. US dollars, milliseconds, percentage points)
- What time boundary or categorical domain is represented
- What interactive mechanisms are available (e.g. "Use arrow keys to step between monthly data points")
Avoid redundant geometry narrations
Do not narrate the physical graphics. Screen-reader users need domain information, not a commentary on SVG shapes:
- Helpful: "Line chart showing gross monthly revenue from January through June 2026, measured in thousands of US dollars. Values range from a low of $42K in January to a peak of $67.4K in May."
- Unhelpful: "This is a chart. The chart contains a curved green path with six circular nodes and horizontal dashed grid lines."
Provide a text summary
For many analytical charts, a concise text summary is significantly more useful to screen-reader users than forcing them to navigate through dozens of individual data points.
Text Summary Paradigm
Screen-reader users benefit far more from concise key insights than traversing 50 isolated SVG paths.
Communicate the insight, not every pixel
A high-quality summary surfaces the core analytical takeaway:
- Direction & Trend: Upward growth, downward contraction, or stable baseline
- Extremes: Maximum peak and minimum floor with corresponding dates/categories
- Total Delta: Net change across the represented period
- Selected State: Currently highlighted or pinned record
// Deterministic summary calculationfunction generateRevenueSummary(data: RevenueDatum[]): string { if (data.length === 0) return "No revenue data available." const min = data.reduce((a, b) => (a.revenue < b.revenue ? a : b)) const max = data.reduce((a, b) => (a.revenue > b.revenue ? a : b)) const start = data[0].revenue const end = data[data.length - 1].revenue const delta = Math.round(((end - start) / start) * 100) return `Revenue grew by ${delta}% overall from January to June. Period peak was $${max.revenue.toLocaleString()} in ${max.month}, with a low of $${min.revenue.toLocaleString()} in ${min.month}.`}Provide a structured data alternative
Data-dense visualizations—such as multi-series line charts, financial candlestick charts, and heatmaps—should offer an accessible tabular alternative.
Accessible Data Alternative
Provide a semantic data table disclosure so power users and screen readers can navigate exact numerical figures.
Single source of truth rule
The data table must consume the identical JavaScript data array passed to the visualization component. Never construct independent, static copies of data for assistive technology:
export function AccessibleChartCard({ data }: { data: MonthlyData[] }) { const [showTable, setShowTable] = useState(false) return ( <section aria-labelledby="sales-metric-title"> <div className="flex items-center justify-between mb-4"> <h3 id="sales-metric-title" className="text-base font-semibold"> Sales Performance </h3> <Button variant="outline" size="sm" onClick={() => setShowTable(!showTable)} aria-expanded={showTable} aria-controls="sales-data-table" > {showTable ? "Hide data table" : "View data table"} </Button> </div> {/* Visual Chart */} <PlotLineChart data={data} /> {/* Semantic Table Alternative */} {showTable && ( <div id="sales-data-table" className="mt-4"> <table className="w-full text-left font-mono text-xs"> <caption className="sr-only">Monthly sales revenue data</caption> <thead> <tr> <th scope="col">Month</th> <th scope="col">Revenue</th> <th scope="col">Variance</th> </tr> </thead> <tbody> {data.map((row) => ( <tr key={row.month}> <th scope="row">{row.month}</th> <td>${row.revenue.toLocaleString()}</td> <td>{row.variance}%</td> </tr> ))} </tbody> </table> </div> )} </section> )}Visible disclosure vs. visually-hidden tables
- Visible Disclosure (
[View data table]): Strongly recommended for analytical and enterprise dashboards. Benefits keyboard users, cognitive accessibility users, and executives who wish to copy numbers into spreadsheets. - Visually-Hidden Table (
sr-only): Appropriate when page layout constraints strictly forbid expanding controls, ensuring screen readers still have structured table navigation keys (T,Ctrl+Alt+Arrows).
Use meaningful semantic structure
Prefer semantic HTML wrapper elements (<section>, <header>, <h3>, <figure>, <figcaption>) over generic unsemantic <div> containers:
<figure role="region" aria-labelledby="figure-caption" className="rounded-xl border border-border p-5"> <PlotLineChart data={chartData} /> <figcaption id="figure-caption" className="text-xs text-muted-foreground mt-3"> Figure 1.1: Gross quarterly sales across North American regions from Q1 2024 to Q2 2026. </figcaption></figure>Marking decorative SVG elements
Non-informational graphical artifacts must be explicitly hidden from screen readers so they do not interrupt reading flow:
{/* Decorative Cartesian Gridlines */}<line x1="0" y1={y} x2={width} y2={y} stroke="var(--border)" aria-hidden="true"/>{/* Decorative Drop Shadow / Glow Filters */}<filter id="decorativeGlow" aria-hidden="true"> <feGaussianBlur stdDeviation="3" /></filter>Keyboard interaction
Any interactive visualization capability offered to mouse cursor users must also be fully operable via keyboard.
Keyboard Navigation Flow (Roving Tabindex)
Interactive ModelEnters the chart component as a single tab stop.
Steps through data points sequentially.
Pins tooltip details or triggers drill-down.
Closes popovers and returns focus to canvas.
The roving tabindex pattern
When a chart renders 30 or 60 data points, do not make every point a separate tab stop (tabIndex={0}). That forces keyboard users to press Tab 60 times just to bypass the chart.
Instead, implement a roving tabindex:
- The outer chart container receives
tabIndex={0}as a single tab stop. - Once focused, arrow keys (
←and→) step between data points internally. EnterorSpaceselects or pins the active point.Escapedismisses any open tooltip card or returns focus to the parent widget.
function handleChartKeyDown(e: React.KeyboardEvent) { switch (e.key) { case "ArrowRight": e.preventDefault() setActiveIndex((prev) => Math.min(prev + 1, data.length - 1)) break case "ArrowLeft": e.preventDefault() setActiveIndex((prev) => Math.max(prev - 1, 0)) break case "Enter": case " ": e.preventDefault() setIsPinned((prev) => !prev) break case "Escape": e.preventDefault() setIsPinned(false) break }}Focus management
Interactive chart controls must feature prominent, unambiguous visual focus indicators.
Focus ring standards
Plotcn charts inherit your application's focus styling:
/* Inherited accessible focus ring */:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px;}Contextual announcements on focus
When keyboard focus moves between data points, provide rich auditory feedback via a live region or accessible text string:
- Helpful: "March 2026, revenue $51,000, 3 of 6."
- Unhelpful: "Point."
Focus persistence across redraws
Theme changes, responsive window resizes, and live-data updates must not arbitrarily reset keyboard focus back to the <body> element. Preserve logical focus on the active data index across component renders.
Tooltips and hover
A tooltip must never be the exclusive mechanism for accessing metric values.
The hover-only anti-pattern
- Flawed Workflow: Sighted mouse hover reveals exact values; keyboard users and mobile touch users see nothing.
- Accessible Workflow: Values are revealed upon hover, upon keyboard
:focus-visible, upon mobile touch tap, and are duplicated inside the accessible data summary or table.
Non-interfering tooltip timing
- Tooltips should not dismiss prematurely when users move the cursor toward them.
- If a tooltip contains interactive actions (e.g. "Drill into May sales"), implement it as an accessible Popover or Dialog component with keyboard focus trap, rather than a transient hover tooltip.
Do not rely on color alone
Up to 8% of male users and 0.5% of female users experience color vision deficiencies. When distinguishing multiple series, color hue must always be accompanied by a secondary, non-color encoding.
Non-Color Visual Encoding
Never convey information by color hue alone. Pair colors with stroke patterns, markers, and direct labels.
Solid primary line with circular geometric vertex marks.
Dashed comparison line with square geometric vertex marks.
Dotted forecast line with triangular warning vertex marks.
Secondary encoding strategies
- Distinct Stroke Dash Patterns:
- Primary: Solid line (
stroke="solid") - Secondary: Dashed line (
strokeDasharray="6 4") - Tertiary: Dotted line (
strokeDasharray="2 3")
- Primary: Solid line (
- Geometric Vertex Markers:
- Series A: Solid circle (
●) - Series B: Solid square (
■) - Series C: Upward triangle (
▲)
- Series A: Solid circle (
- Signed Numbers & Directional Glyphs:
- Positive Gain:
+$14,200accompanied by an upward green indicator (▲) - Negative Loss:
-$8,500accompanied by a downward red indicator (▼)
- Positive Gain:
- Direct Line Labels: Place text labels at the terminal end of lines rather than relying exclusively on a detached legend box.
Motion and reduced motion
Vestibular disorders can make animations—such as sweeping line entries, pulsing glows, and bouncing physics simulations—disorienting or nauseating.
Plotcn strictly honors the OS-level prefers-reduced-motion media query:
@media (prefers-reduced-motion: reduce) { /* Immediately resolve transitions and line sweeps */ *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }}Engine-specific reduced motion implementations
- Recharts: Pass
isAnimationActive={false}whenprefers-reduced-motionis detected. - D3.js: Render directly into the final coordinate scales. Never architect a visualization such that the animated transition is the only code path computing geometric attributes.
- Google Charts: Map
animation.duration: 0inside the chart configuration options object.
Dynamic data updates
Real-time streaming dashboards, polling intervals, and user filters trigger frequent visual updates.
Discrete live announcements
Do not wrap the entire visualization inside aria-live="assertive". Announcing every coordinate redraw spams screen readers and overwhelms users.
Instead, announce only discrete, meaningful state changes:
<div aria-live="polite" className="sr-only"> {filterStatusText}</div>- Good: "Filtered by North America: 14 regional nodes displayed."
- Bad: "Chart redraw complete." (repeated every 2 seconds on polling).
Loading, empty, and error states
All non-ready visualization states must provide semantic clarity to assistive technologies.
Truthful loading states
- Mark the chart container with
aria-busy="true"andaria-label="Loading revenue data...". - Never render synthetic placeholder numbers during loading. Screen-reader users may interpret placeholder digits as factual business data.
Contextual empty states
Explain what criteria produced the empty state:
- Good: "No transactions recorded between March 1 and March 15, 2026."
- Bad: "No data."
Actionable error states
Provide the failure reason and an accessible button to retry the fetch:
<div role="alert" className="rounded-xl border border-destructive/40 bg-destructive/10 p-6 text-center"> <p className="text-sm font-semibold text-destructive">Failed to load revenue metrics</p> <p className="text-xs text-muted-foreground mt-1">Unable to connect to the analytics cluster.</p> <Button variant="outline" size="sm" onClick={retryFetch} className="mt-3"> Retry connection </Button></div>Recharts accessibility
Recharts generates standard SVG DOM nodes, but does not provide an accessible experience out of the box.
Plotcn wrapping pattern for Recharts
When using Recharts:
- Wrap the
<ResponsiveContainer>in a semantic<section>witharia-labelledbyandaria-describedby. - Connect a deterministic
<ChartSummary />component adjacent to the chart. - Provide an
<AccessibleDataDisclosure />table. - Disable entrance animations via
isAnimationActive={!prefersReducedMotion}.
D3.js accessibility
D3 provides total control over DOM and SVG generation, which gives developers greater responsibility.
The D3 architectural rule
Use D3 for mathematics and layout scales; use React for semantic DOM and SVG rendering.
By allowing React to render the SVG elements, you maintain standard React JSX props (aria-label, tabIndex, onKeyDown, :focus-visible) rather than mutating imperative DOM trees with D3 selections.
For dense charts with over 1,000 nodes, do not render 1,000 focusable elements. Use binning, aggregated cluster summaries, and an accessible data table alternative.
Google Charts accessibility
Google Charts renders inside an internal sandboxed DOM or <iframe>, placing its internal structure outside of direct React prop control.
The external accessible shell strategy
Because Google Charts manages its own internal SVG tags, Plotcn provides an external accessible shell:
- Render a clean semantic wrapper with
aria-labelledby,aria-describedby, and the text summary outside the Google container<div>. - Intercept Google Charts
selectevents and mirror the selected datum into standard React state. - Provide a structured data table disclosure so assistive technologies can read data points directly from your original JavaScript array without touching Google's internal SVG.
- On Google GeoChart, never rely on choropleth color intensity alone; provide region name labels and numerical tables.
Responsive layout and zoom
Accessible visualizations must accommodate users who view dashboards on mobile devices or utilize 400% browser zoom.
- Avoid Fixed Pixel Widths: Always use fluid responsive containers (
100%width). - Graceful Decluttering: On small viewports or at high zoom levels, automatically reduce tick counts (e.g. display every second month rather than all twelve) to prevent label overlapping.
- No Two-Dimensional Scrolling: Ensure charts scale vertically or provide internal card horizontal scrolling without forcing the global browser window to scroll horizontally.
Test accessibility
Accessibility verification requires both automated scanning and manual assistive-technology testing.
Plotcn Visualization Accessibility Verification Matrix
Engineering QA Protocol| Category | Accessibility Check | Audit Method | Engineering Tier |
|---|---|---|---|
| Naming & Labels | Accessible Name & Description Inspect root SVG/section for aria-labelledby and aria-describedby pointing to valid, stable DOM IDs. | Screen Reader & DOM Inspection | Mandatory for All Charts |
| Insights | Natural-Language Data Summary Verify concise sentence surfacing overall direction, peak month/value, minimum, and delta percentage. | Text / DOM Audit | Mandatory for Key Charts |
| Data Alternative | Semantic Table Alternative Confirm table contains matching numeric values from the single source array with scoped <th> cells. | Screen Reader Table Keys (T / Ctrl+Alt+Arrows) | Recommended for Multi-series |
| Keyboard | Roving Tabindex Navigation Ensure chart enters as 1 tab stop. Left/Right arrows move through data points with visible 2px outline. | Keyboard Only (Tab, ← / →, Enter, Esc) | Mandatory for Interactive |
| Visual Encoding | Non-Color Data Distinction Switch viewport to grayscale. Confirm series remain distinguishable via dash patterns, markers, and direct labels. | Grayscale / Monochrome Filter | Mandatory for Multi-series |
| Motion | prefers-reduced-motion Support Emulate reduced motion. Chart must render directly into final coordinates without sweeping or bouncing. | DevTools Emulation (Reduced Motion) | Mandatory if Animated |
| States | Truthful Loading / Empty / Error Verify aria-busy during loading (no fake data), helpful error descriptions, and accessible retry buttons. | Network Throttling & Mock Failure | Mandatory for Async Charts |
| Responsive Zoom | 400% Browser Zoom Inspection Confirm content does not produce two-dimensional page scrolling; labels declutter gracefully. | Browser Zoom at 400% (320px layout) | Mandatory for Responsive |
Practical QA testing protocol
- Keyboard-Only Audit: Unplug or disable the mouse. Can you reach the chart, step between points with arrow keys, inspect metrics, and dismiss popovers with Escape?
- Screen Reader Verification: Test using NVDA (Windows), VoiceOver (macOS/iOS), or TalkBack (Android). Does the synthesizer announce the title, description, and natural-language summary clearly?
- Monochrome Audit: View the chart in grayscale or with Windows High Contrast Mode. Are all series still distinct?
- Reduced Motion Emulation: Open DevTools, emulate
prefers-reduced-motion: reduce, and confirm animations immediately settle into final positions. - Edge Case Datasets: Test with empty arrays, single-item arrays, negative values, and zero baselines.
Common accessibility mistakes
Common Visualization Accessibility Mistakes
Diagnostic Checklistaria-label Alone is Incomplete
PROBLEM:Setting aria-label='Revenue Chart' and assuming the visualization is fully accessible.
WHY:A screen-reader user still has zero awareness of the trend, the numbers, the peak month, or the underlying dataset.
50 SVG Nodes as 50 Tab Stops
PROBLEM:Making every path, circle, or bar element focusable with tabIndex={0}.
WHY:Tabbing through 50 data points becomes exhausting and pollutes the global document tab sequence.
Hover-Only Tooltip Metric Data
PROBLEM:Exact coordinate values and percentages only appear upon mouse cursor hover.
WHY:Keyboard users, voice-control users, and mobile touch users can never reach or inspect that data.
Color as Sole Data Distinction
PROBLEM:Distinguishing three lines solely by green, blue, and orange hues.
WHY:Color-blind users (~8% of males) and users in high-glare environments cannot tell which line is which.
Noisy Live Regions on Every Redraw
PROBLEM:Placing an entire interactive chart inside aria-live='polite' or 'assertive'.
WHY:Screen readers spam the user with continuous coordinate announcements on every cursor jitter or frame refresh.
Focus Dropped on Chart Redraw
PROBLEM:Re-rendering or swapping themes completely unmounts the chart and drops keyboard focus back to <body>.
WHY:Disorients keyboard users and forces them to re-navigate the entire page to find their place.
Recommended accessibility workflow
Follow this repeatable 12-step engineering protocol whenever you install or build a Plotcn visualization:
12-Step Accessible Visualization Delivery Sequence
Engineering ProtocolIdentify the primary insight or trend the chart is intended to communicate.
Attach an explicit heading (<h2> or <h3>) with a stable, server-safe ID.
Write a concise sentence specifying units, timeline, and scope via aria-describedby.
Surface directional trend, peak, floor, and total change in natural language.
Provide an accessible semantic table disclosure sharing the same underlying data array.
Configure 1 Tab stop into chart; roving Arrow keys (←/→) to inspect data points.
Style :focus-visible with a high-contrast 2px outline and 2px offset.
Ensure all metric tooltips can be triggered via keyboard focus and mobile tap.
Audit in grayscale: add distinct stroke dashes, geometric markers, and direct labels.
Bypass line sweeps and spring physics under prefers-reduced-motion: reduce.
Audit truthful loading skeletons, informative empty states, and actionable retry errors.
Verify live readout using VoiceOver (macOS/iOS) or NVDA (Windows) before shipping.
Accessible Name + Description + Text Summary
+ Semantic Data Table Alternative
+ Roving Tabindex + Visible Focus + Touch
+ Instant Reduced Motion Fallback
Next steps
Now that your visualizations communicate accessibly across all sensory and input modes, explore specific engine guides and pre-built components: